diff --git a/assets/services/services_rom.bin b/assets/services/services_rom.bin index 51c8fc3..f0d5ec7 100644 Binary files a/assets/services/services_rom.bin and b/assets/services/services_rom.bin differ diff --git a/guest/services/copperline_board.h b/guest/services/copperline_board.h index b81e0a3..bae2952 100644 --- a/guest/services/copperline_board.h +++ b/guest/services/copperline_board.h @@ -30,7 +30,6 @@ #define ROM_OFFSET 0x0008 #define MOUNTS_OFFSET 0x3800 #define MOUNT_ENTRY_SIZE 32 -#define MOUNT_MAX_COUNT 16 #define VOLUMES_OFFSET 0x7000 #define VOLUME_SLOT_SIZE 128 // Per-unit FileSysStartupMsg, written by the emulator at expansion init; diff --git a/guest/services/handler.c b/guest/services/handler.c index 40b5539..dcf3ff9 100644 --- a/guest/services/handler.c +++ b/guest/services/handler.c @@ -58,16 +58,19 @@ static struct ExecBase *sysbase(void) // Hand the packet to the emulator, which fills dp_Res1/dp_Res2. Returns a // TRAP_RES_* code; for TRAP_RES_ADDVOLUME the host also returns the volume -// DosList node it built in *vol (via A0). +// DosList node it built in *vol (via A0). The mount unit (D2) tells the host +// which mount this packet is for, so it routes by unit rather than by the +// handler's MsgPort address. static ULONG trap_packet(struct DosPacket *pkt, struct MsgPort *port, - struct DosList **vol) + LONG unit, struct DosList **vol) { register ULONG res __asm("d0"); register struct DosList *_vol __asm("a0"); register struct DosPacket *_pkt __asm("d1") = pkt; + register LONG _unit __asm("d2") = unit; register struct MsgPort *_port __asm("a1") = port; __asm volatile(".short 0xA402" // TRAP_PACKET - : "=r"(res), "=r"(_vol), "+r"(_pkt), "+r"(_port) + : "=r"(res), "=r"(_vol), "+r"(_pkt), "+r"(_unit), "+r"(_port) : : "cc", "memory"); *vol = _vol; @@ -80,14 +83,23 @@ void handler_main(void) struct Library *_dosbase = OpenLibrary((STRPTR) "dos.library", 36); struct Process *me = (struct Process *)FindTask(NULL); struct MsgPort *port = &me->pr_MsgPort; + LONG unit = -1; for (;;) { WaitPort(port); struct Message *msg; while ((msg = GetMsg(port)) != NULL) { struct DosPacket *pkt = (struct DosPacket *)msg->mn_Node.ln_Name; + // The first packet is ACTION_STARTUP (== ACTION_NIL == 0): dp_Arg3 + // is our DeviceNode, whose dn_Startup FileSysStartupMsg holds our + // mount unit. Cache it once and pass it on every packet. + if (unit < 0) { + struct DeviceNode *dn = BADDR(pkt->dp_Arg3); + struct FileSysStartupMsg *fssm = BADDR(dn->dn_Startup); + unit = fssm->fssm_Unit; + } struct DosList *vol; - ULONG res = trap_packet(pkt, port, &vol); + ULONG res = trap_packet(pkt, port, unit, &vol); if (res != TRAP_RES_NOREPLY) { struct MsgPort *reply = pkt->dp_Port; pkt->dp_Port = port; @@ -118,10 +130,9 @@ void handler_main(void) void mount_boards(UBYTE *board, struct Library *_expbase, struct ConfigDev *cd) { struct ExecBase *_sysbase = sysbase(); + // The host writes count into the mount table and bounds it (board_image). UWORD count = *(UWORD *)(board + MOUNTS_OFFSET); - if (count > MOUNT_MAX_COUNT) - count = MOUNT_MAX_COUNT; for (UWORD i = 0; i < count; i++) { const UBYTE *name = board + MOUNTS_OFFSET + 2 + (ULONG)i * MOUNT_ENTRY_SIZE; diff --git a/src/filesys.rs b/src/filesys.rs index f33ec9c..fdd9b6b 100644 --- a/src/filesys.rs +++ b/src/filesys.rs @@ -33,7 +33,9 @@ pub const ROM_OFFSET: usize = 0x0008; /// Mount table: u16 count, then fixed-size NUL-terminated device names. pub const MOUNTS_OFFSET: usize = 0x3800; pub const MOUNT_ENTRY_SIZE: usize = 32; -pub const MOUNT_MAX_COUNT: usize = 16; +/// Maximum host mounts (units), and the divisor for each unit's fixed +/// board-window lock-pool slice. +pub const MOUNT_MAX_COUNT: usize = 8; /// The DiagArea (`BoardSpec::copperline_services` points er_InitDiagVec /// here): embedded in the handler ROM at +0x40, like real autoboot boards /// carry theirs in the device ROM (see `_diag_area` in entry.s). @@ -130,17 +132,100 @@ enum GuestOp { Die(u32), } -/// A handed-out FileLock: which mount it belongs to and the path relative to -/// the mount root (empty = the root itself). Keyed by the guest address of -/// the FileLock structure in the board-window pool. +/// A handed-out FileLock: the path relative to its unit's mount root (empty = +/// the root itself). Keyed by the guest address of the FileLock structure in +/// the board-window pool, and always stored in its owning unit. #[derive(Debug, Clone)] struct LockRec { - unit: usize, rel: PathBuf, } +/// One unit's slice of the board-window FileLock pool: a bump cursor over the +/// board-relative range up to `end`, plus a free list of recycled absolute +/// addresses. Slices are fixed and non-overlapping (see `set_mounts`), so units +/// never share allocator state. +struct LockPool { + /// Board-relative bump cursor. + next: u32, + /// Board-relative end of this unit's slice. + end: u32, + /// Recycled slot addresses (absolute), reused before bumping. + free: Vec, +} + +impl LockPool { + fn new(base: u32, end: u32) -> Self { + Self { + next: base, + end, + free: Vec::new(), + } + } + + /// Hand out one `LOCK_SLOT_SIZE` slot as an absolute guest address, or None + /// once this unit's slice is exhausted. + fn alloc(&mut self, board_base: u32) -> Option { + self.free.pop().or_else(|| { + (self.next + LOCK_SLOT_SIZE <= self.end).then(|| { + let addr = board_base + self.next; + self.next += LOCK_SLOT_SIZE; + addr + }) + }) + } + + /// Return a freed slot (absolute address) for reuse. + fn release(&mut self, addr: u32) { + self.free.push(addr); + } +} + +/// Per-mount state: the immutable [`MountSpec`] from config plus everything the +/// handler learns or hands out at run time, including this unit's slice of the +/// board-window lock pool. `FilesysHle` owns one per unit, indexed by unit +/// number, so all per-unit state lives in one place and the packet handler is +/// a method on the unit. +struct FilesysUnit { + /// Unit number: this mount's `HOSTFS` device and board-window slot. + index: usize, + mount: MountSpec, + /// Handler MsgPort, captured from the startup packet; stamped into the + /// dn_Task/dol_Task/fl_Task fields DOS uses to reach the handler. + port: Option, + /// Guest address of the DeviceNode (from the startup packet); also the + /// "handler started" marker, cleared at ACTION_DIE. + device_node: Option, + /// Guest address of the volume DosList node the host built. + volume: Option, + /// Open files by fh_Arg1 cookie (host-side only, no guest structure). + /// The LockRec remembers what the handle refers to, for EXAMINE_FH. + files: HashMap, + /// Cookie counter for this unit's open files (unique within the unit). + next_file_key: u32, + /// Guest FileLock address -> what it locks. + locks: HashMap, + /// This unit's fixed slice of the board-window FileLock pool. + pool: LockPool, +} + +impl FilesysUnit { + fn new(index: usize, mount: MountSpec, pool_base: u32, pool_end: u32) -> Self { + Self { + index, + mount, + port: None, + device_node: None, + volume: None, + files: HashMap::new(), + next_file_key: 0, + locks: HashMap::new(), + pool: LockPool::new(pool_base, pool_end), + } + } +} + /// Host side of the filesys trap gateway: implements the AmigaDOS packet -/// ACTION_* semantics against the host directories in `mounts`. +/// ACTION_* semantics against the host directories in `units`. /// /// "Hle" is the m68k crate's HleHandler trait: High-Level Emulation, the /// hook that intercepts reserved opcodes on the host side instead of letting @@ -149,62 +234,74 @@ struct LockRec { /// with no mounts configured changes nothing. #[derive(Default)] pub struct FilesysHle { - mounts: Vec, + /// Per-mount state, indexed by unit number (built from the config mounts + /// in `set_mounts`). + units: Vec, /// Board base address, captured from A0 at the DiagPoint trap. board_base: Option, - /// Handler MsgPort address -> mount unit, learned from startup packets. - /// All per-boot state is cleared at the TRAP_DIAG_ENTRY of the next - /// boot (expansion init runs exactly once per boot). - ports: HashMap, - /// Mount unit -> guest address of its volume DosList node. - volumes: HashMap, - /// Mount unit -> guest address of its DeviceNode (from the startup - /// packet), for clearing dn_Task at ACTION_DIE. - device_nodes: HashMap, - /// Open files by fh_Arg1 cookie (host-side only, no guest structure). - /// The LockRec remembers what the handle refers to, for EXAMINE_FH. - files: HashMap, - next_file_key: u32, - /// Guest FileLock address -> what it locks. - locks: HashMap, - /// Free slots in the board-window lock pool (guest addresses). - free_slots: Vec, - /// Bump allocator behind `free_slots`, board-relative. - pool_next: u32, } impl FilesysHle { pub fn set_mounts(&mut self, mounts: Vec) { - self.mounts = mounts; + // Give each unit a fixed, non-overlapping slice of the board-window + // lock pool. Size the slices by the board's *maximum* unit count, not + // the current mount count, so a unit's slice stays at the same offset + // no matter how many are active -- outstanding lock addresses must not + // move when units are added or removed at runtime (the eventual + // mount/eject-on-the-fly goal). + let chunk = (POOL_END - POOL_OFFSET) / MOUNT_MAX_COUNT as u32; + self.units = mounts + .into_iter() + .enumerate() + .map(|(i, mount)| { + let base = POOL_OFFSET + i as u32 * chunk; + FilesysUnit::new(i, mount, base, base + chunk) + }) + .collect(); } + /// Dispatch a DosPacket to its unit; returns (dp_Res1, dp_Res2). `unit` + /// comes from the handler (D2); `board_base` is stamped in from here so + /// the units need not each carry it. + fn handle_packet( + &mut self, + bus: &mut dyn AddressBus, + unit: usize, + port: u32, + pkt: u32, + guest_op: &mut Option, + ) -> (u32, u32) { + if unit >= self.units.len() { + log::warn!("filesys: packet for unknown unit {unit}"); + return (DOSFALSE, ERROR_OBJECT_NOT_FOUND); + } + let base = self.board_base.expect("packet before DiagPoint"); + self.units[unit].handle_packet(bus, base, port, pkt, guest_op) + } +} + +impl FilesysUnit { /// Host path a lock refers to. fn lock_path(&self, rec: &LockRec) -> PathBuf { - self.mounts[rec.unit].path.join(&rec.rel) + self.mount.path.join(&rec.rel) } - /// Allocate a FileLock in the board-window pool and register it. + /// Allocate a FileLock in this unit's board-window sub-pool and register + /// it. The handler port and volume node come from the unit. fn alloc_lock( &mut self, bus: &mut dyn AddressBus, - port: u32, + board_base: u32, access: u32, rec: LockRec, ) -> Option { - let base = self.board_base?; - let addr = self.free_slots.pop().or_else(|| { - let next = POOL_OFFSET + self.pool_next; - (next + LOCK_SLOT_SIZE <= POOL_END).then(|| { - self.pool_next += LOCK_SLOT_SIZE; - base + next - }) - })?; + let addr = self.pool.alloc(board_base)?; let lock = FileLock { link: long(0), key: long(addr), access: long(access), - task: long(port), - volume: long(self.volumes.get(&rec.unit).copied().unwrap_or(0) >> 2), + task: long(self.port.unwrap_or(0)), + volume: long(self.volume.unwrap_or(0) >> 2), }; write_bytes(bus, addr, lock.as_bytes()); self.locks.insert(addr, rec); @@ -215,12 +312,12 @@ impl FilesysHle { /// semantics: an optional `prefix:` is stripped (the supplied lock is /// already the base it named), `/` goes to the parent, and names are /// case-insensitive. - fn resolve(&self, unit: usize, lock_bptr: u32, name: &[u8]) -> Option { - let (unit, mut rel) = if lock_bptr != 0 { - let rec = self.locks.get(&(lock_bptr << 2))?; - (rec.unit, rec.rel.clone()) + fn resolve(&self, lock_bptr: u32, name: &[u8]) -> Option { + // A lock handed to this unit's handler always belongs to this unit. + let mut rel = if lock_bptr != 0 { + self.locks.get(&(lock_bptr << 2))?.rel.clone() } else { - (unit, PathBuf::new()) + PathBuf::new() }; let mut rest = name; @@ -238,7 +335,7 @@ impl FilesysHle { if rest.is_empty() { // Bare "DEVICE:" or an empty name: the base itself. (split() // below would yield one empty component = "parent", wrong.) - return Some(LockRec { unit, rel }); + return Some(LockRec { rel }); } // A single trailing '/' does not mean parent: "Sub/" is Sub itself // (the "directory part" convention; verified against FFS, where @@ -256,10 +353,10 @@ impl FilesysHle { continue; } let comp = String::from_utf8_lossy(comp).into_owned(); - let dir = self.mounts.get(unit)?.path.join(&rel); + let dir = self.mount.path.join(&rel); rel.push(match_component(&dir, &comp)?); } - Some(LockRec { unit, rel }) + Some(LockRec { rel }) } /// Fill a FileInfoBlock from a host path. @@ -273,7 +370,7 @@ impl FilesysHle { let path = self.lock_path(rec); let meta = std::fs::metadata(&path).map_err(|_| ERROR_OBJECT_NOT_FOUND)?; let name: String = if rec.rel.as_os_str().is_empty() { - self.mounts[rec.unit].volume.clone() + self.mount.volume.clone() } else { rec.rel .file_name() @@ -336,7 +433,9 @@ impl FilesysHle { names.sort(); names } +} +impl FilesysHle { /// Write one FileSysStartupMsg per mount into the board window, plus the /// shared display device name and the per-unit DosEnvecs they reference. /// dn_Startup points at these so the Early Startup boot menu shows @@ -345,7 +444,8 @@ impl FilesysHle { /// to AddBootNode. fn write_startup_msgs(&self, bus: &mut dyn AddressBus, base: u32) { write_bytes(bus, base + FSSM_DEVNAME_OFFSET, &bcpl::<32>(b"hostfs")); - for (unit, mount) in self.mounts.iter().enumerate() { + for (unit, u) in self.units.iter().enumerate() { + let mount = &u.mount; let unit = unit as u32; let envec = DosEnvec { table_size: long(16), // entries after this one, through dos_type @@ -381,19 +481,20 @@ impl FilesysHle { ); } } +} - /// Build the volume DosList node for `unit` in the board window; the - /// guest handler AddDosEntry's it (only guest code may take the DosList +impl FilesysUnit { + /// Build this unit's volume DosList node in the board window; the guest + /// handler AddDosEntry's it (only guest code may take the DosList /// semaphore). Returns the node's guest address. - fn build_volume_node(&mut self, bus: &mut dyn AddressBus, unit: usize, port: u32) -> u32 { - let base = self.board_base.expect("startup packet before DiagPoint"); - let vol = base + VOLUMES_OFFSET + unit as u32 * VOLUME_SLOT_SIZE; + fn build_volume_node(&mut self, bus: &mut dyn AddressBus, board_base: u32) -> u32 { + let vol = board_base + VOLUMES_OFFSET + self.index as u32 * VOLUME_SLOT_SIZE; let fixed = std::mem::size_of::() as u32; let (days, mins, ticks) = amiga_datestamp(Some(std::time::SystemTime::now())); let node = VolumeNode { next: long(0), r#type: long(2), // DLT_VOLUME - task: long(port), + task: long(self.port.unwrap_or(0)), lock: long(0), volume_date: [long(days), long(mins), long(ticks)], lock_list: long(0), @@ -402,18 +503,22 @@ impl FilesysHle { name: long((vol + fixed) >> 2), // BSTR right after the struct }; write_bytes(bus, vol, node.as_bytes()); - let name: Vec = self.mounts[unit].volume.bytes().take(30).collect(); + let name: Vec = self.mount.volume.bytes().take(30).collect(); write_bytes(bus, vol + fixed, &bcpl::<32>(&name)); - self.volumes.insert(unit, vol); + self.volume = Some(vol); vol } - /// Handle one DosPacket; returns (dp_Res1, dp_Res2). Some packets also - /// need DosList surgery only the guest may perform (the semaphore); - /// `guest_op` tells the handler what to do after replying. + /// Handle one DosPacket for this unit; returns (dp_Res1, dp_Res2). Some + /// packets also need DosList surgery only the guest may perform (the + /// semaphore); `guest_op` tells the handler what to do after replying. + /// `port` is the handler's MsgPort, captured at the startup packet and + /// stamped into the dn_Task/dol_Task/fl_Task fields DOS uses to reach the + /// handler; `board_base` locates this unit's board-window structures. fn handle_packet( &mut self, bus: &mut dyn AddressBus, + board_base: u32, port: u32, pkt: u32, guest_op: &mut Option, @@ -421,31 +526,27 @@ impl FilesysHle { let dp_type = bus.read_long(pkt + 8) as i32; let arg = |bus: &mut dyn AddressBus, n: u32| bus.read_long(pkt + 20 + 4 * (n - 1)); - // The first packet on a port is the startup packet DOS sends when it - // starts the handler process (its dp_Type is not meaningful). - if !self.ports.contains_key(&port) { + // The first packet is the startup packet (ACTION_STARTUP, synonymous + // with ACTION_NIL == 0): the handler passes its unit in on every + // packet, so the first one we see is the one DOS sent to start this + // unit's process. dp_Arg3 is the DeviceNode; capture the handler + // MsgPort, wire dn_Task to it, and hand the guest the volume node to + // AddDosEntry. + if self.device_node.is_none() { let dn = arg(bus, 3) << 2; // dp_Arg3: BPTR DeviceNode - // dn_Startup is a BPTR to the unit's FileSysStartupMsg (written - // by write_startup_msgs); fssm_Unit is its first field. - let fssm = bus.read_long(dn + DEVICENODE_STARTUP) << 2; - let unit = bus.read_long(fssm) as usize; - if unit >= self.mounts.len() { - log::warn!("filesys: startup packet for unknown unit {unit}"); - return (DOSFALSE, ERROR_OBJECT_NOT_FOUND); - } bus.write_long(dn + DEVICENODE_TASK, port); - self.ports.insert(port, unit); - self.device_nodes.insert(unit, dn); - *guest_op = Some(GuestOp::AddVolume(self.build_volume_node(bus, unit, port))); + self.device_node = Some(dn); + self.port = Some(port); + let vol = self.build_volume_node(bus, board_base); + *guest_op = Some(GuestOp::AddVolume(vol)); log::info!( "filesys: {}: handler started ({}: -> {})", - device_name(unit), - self.mounts[unit].volume, - self.mounts[unit].path.display() + device_name(self.index), + self.mount.volume, + self.mount.path.display() ); return (DOSTRUE, 0); } - let unit = self.ports[&port]; match dp_type { ACTION_IS_FILESYSTEM => (DOSTRUE, 0), @@ -456,27 +557,26 @@ impl FilesysHle { // Like the UAE filesys, report the size and free space of the // host filesystem holding the mount (statvfs), scaled so the // block counts survive AmigaDOS's 32-bit arithmetic. - let (total, avail) = - host_fs_usage(&self.mounts[unit].path).unwrap_or((1 << 30, 1 << 29)); + let (total, avail) = host_fs_usage(&self.mount.path).unwrap_or((1 << 30, 1 << 29)); let (blocksize, numblocks, inuse) = scale_blocks(total, avail); - let locks_open = self.locks.values().any(|l| l.unit == unit); + let locks_open = !self.locks.is_empty(); let info = InfoData { num_soft_errors: long(0), - unit_number: long(unit as u32), + unit_number: long(self.index as u32), // Read-only for now: shows as "Read Only" in C:Info. disk_state: long(ID_WRITE_PROTECTED), num_blocks: long(numblocks), num_blocks_used: long(inuse), bytes_per_block: long(blocksize), disk_type: long(ID_CLFS_DISK), - volume_node: long(self.volumes.get(&unit).copied().unwrap_or(0) >> 2), + volume_node: long(self.volume.unwrap_or(0) >> 2), in_use: long(if locks_open { DOSTRUE } else { 0 }), }; write_bytes(bus, id, info.as_bytes()); log::debug!( "filesys: {}: InfoData at {id:#010X}: blocks={numblocks} \ used={inuse} bs={blocksize} (host total={total} avail={avail})", - device_name(unit) + device_name(self.index) ); (DOSTRUE, 0) } @@ -485,18 +585,18 @@ impl FilesysHle { let name = read_bstr(bus, name_bptr); log::debug!( "filesys: {}: locate \"{}\" (lock {:#X})", - device_name(unit), + device_name(self.index), String::from_utf8_lossy(&name), arg(bus, 1) ); - let Some(rec) = self.resolve(unit, arg(bus, 1), &name) else { + let Some(rec) = self.resolve(arg(bus, 1), &name) else { return (DOSFALSE, ERROR_OBJECT_NOT_FOUND); }; if !self.lock_path(&rec).exists() { return (DOSFALSE, ERROR_OBJECT_NOT_FOUND); } let access = arg(bus, 3); - match self.alloc_lock(bus, port, access, rec) { + match self.alloc_lock(bus, board_base, access, rec) { Some(addr) => (addr >> 2, 0), None => (DOSFALSE, ERROR_OBJECT_NOT_FOUND), } @@ -504,7 +604,7 @@ impl FilesysHle { ACTION_FREE_LOCK => { let addr = arg(bus, 1) << 2; if addr != 0 && self.locks.remove(&addr).is_some() { - self.free_slots.push(addr); + self.pool.release(addr); } (DOSTRUE, 0) } @@ -531,7 +631,6 @@ impl FilesysHle { return (DOSFALSE, ERROR_NO_MORE_ENTRIES); }; let child = LockRec { - unit: rec.unit, rel: rec.rel.join(name), }; match self.fill_fib(bus, fib, &child, index as u32 + 1) { @@ -545,7 +644,6 @@ impl FilesysHle { let bptr = arg(bus, 1); let rec = if bptr == 0 { LockRec { - unit, rel: PathBuf::new(), } } else { @@ -554,7 +652,7 @@ impl FilesysHle { None => return (DOSFALSE, ERROR_INVALID_LOCK), } }; - match self.alloc_lock(bus, port, ACCESS_READ, rec) { + match self.alloc_lock(bus, board_base, ACCESS_READ, rec) { Some(addr) => (addr >> 2, 0), None => (DOSFALSE, ERROR_OBJECT_NOT_FOUND), } @@ -570,11 +668,8 @@ impl FilesysHle { } let mut rel = rec.rel; rel.pop(); - let parent = LockRec { - unit: rec.unit, - rel, - }; - match self.alloc_lock(bus, port, ACCESS_READ, parent) { + let parent = LockRec { rel }; + match self.alloc_lock(bus, board_base, ACCESS_READ, parent) { Some(addr) => (addr >> 2, 0), None => (DOSFALSE, ERROR_OBJECT_NOT_FOUND), } @@ -587,7 +682,7 @@ impl FilesysHle { // support lands, so protection round-trips. let name_bptr = arg(bus, 3); let name = read_bstr(bus, name_bptr); - match self.resolve(unit, arg(bus, 2), &name) { + match self.resolve(arg(bus, 2), &name) { Some(rec) if self.lock_path(&rec).exists() => (DOSTRUE, 0), _ => (DOSFALSE, ERROR_OBJECT_NOT_FOUND), } @@ -600,11 +695,11 @@ impl FilesysHle { let name = read_bstr(bus, name_bptr); log::debug!( "filesys: {}: open \"{}\" (lock {:#X})", - device_name(unit), + device_name(self.index), String::from_utf8_lossy(&name), arg(bus, 2) ); - let Some(rec) = self.resolve(unit, arg(bus, 2), &name) else { + let Some(rec) = self.resolve(arg(bus, 2), &name) else { return (DOSFALSE, ERROR_OBJECT_NOT_FOUND); }; let path = self.lock_path(&rec); @@ -628,7 +723,6 @@ impl FilesysHle { let rec_of = |hle: &Self, bptr: u32| -> Option { if bptr == 0 { Some(LockRec { - unit, rel: PathBuf::new(), }) } else { @@ -639,7 +733,9 @@ impl FilesysHle { else { return (DOSFALSE, ERROR_INVALID_LOCK); }; - if a.unit == b.unit && a.rel == b.rel { + // Both locks belong to this unit, so equal paths mean the same + // object. + if a.rel == b.rel { (DOSTRUE, 0) } else { (DOSFALSE, 0) @@ -665,7 +761,7 @@ impl FilesysHle { self.files.insert(key, (f, rec)); bus.write_long(fh + FILEHANDLE_ARG1, key); self.locks.remove(&lock_addr); - self.free_slots.push(lock_addr); + self.pool.release(lock_addr); (DOSTRUE, 0) } Err(_) => (DOSFALSE, ERROR_OBJECT_NOT_FOUND), @@ -680,11 +776,8 @@ impl FilesysHle { }; let mut rel = rec.rel; rel.pop(); - let parent = LockRec { - unit: rec.unit, - rel, - }; - match self.alloc_lock(bus, port, ACCESS_READ, parent) { + let parent = LockRec { rel }; + match self.alloc_lock(bus, board_base, ACCESS_READ, parent) { Some(addr) => (addr >> 2, 0), None => (DOSFALSE, ERROR_OBJECT_NOT_FOUND), } @@ -774,20 +867,22 @@ impl FilesysHle { // Shut the handler down (dismount tools send this; stock // Assign DISMOUNT only unlinks the DeviceNode). Refuse // while anything is held open, like a real handler. - let in_use = self.locks.values().any(|l| l.unit == unit) - || self.files.values().any(|(_, r)| r.unit == unit); - if in_use { + if !self.locks.is_empty() || !self.files.is_empty() { return (DOSFALSE, ERROR_OBJECT_IN_USE); } // Clear dn_Task so the next reference to the device simply - // restarts the handler (and re-adds the volume). - if let Some(dn) = self.device_nodes.remove(&unit) { + // restarts the handler (and re-adds the volume). Dropping + // device_node/port marks the unit un-started again. + if let Some(dn) = self.device_node.take() { bus.write_long(dn + DEVICENODE_TASK, 0); } - self.ports.remove(&port); - let vol = self.volumes.remove(&unit).unwrap_or(0); + self.port = None; + let vol = self.volume.take().unwrap_or(0); *guest_op = Some(GuestOp::Die(vol)); - log::info!("filesys: {}: ACTION_DIE, handler exits", device_name(unit)); + log::info!( + "filesys: {}: ACTION_DIE, handler exits", + device_name(self.index) + ); (DOSTRUE, 0) } // Write-family actions: mounts are read-only for now, so the @@ -799,7 +894,10 @@ impl FilesysHle { (DOSFALSE, ERROR_DISK_WRITE_PROTECTED) } _ => { - log::debug!("filesys: {}: unhandled action {dp_type}", device_name(unit)); + log::debug!( + "filesys: {}: unhandled action {dp_type}", + device_name(self.index) + ); (DOSFALSE, ERROR_ACTION_NOT_KNOWN) } } @@ -823,31 +921,33 @@ impl HleHandler for FilesysHle { // keeping only the configured mounts, so a newly added // per-boot field can never be left un-reset (this is how // next_file_key came to be missed). - let mounts = std::mem::take(&mut self.mounts); - *self = FilesysHle { - mounts, - board_base: Some(cpu.dar[8]), - ..Self::default() - }; + let mounts: Vec = std::mem::take(&mut self.units) + .into_iter() + .map(|u| u.mount) + .collect(); + *self = FilesysHle::default(); + self.set_mounts(mounts); + self.board_base = Some(cpu.dar[8]); self.write_startup_msgs(bus, cpu.dar[8]); log::info!( "filesys: expansion init at board {:#010X}, {} mount(s)", cpu.dar[8], - self.mounts.len() + self.units.len() ); true } TRAP_PACKET => { let pkt = cpu.dar[1]; // D1 + let unit = cpu.dar[2] as usize; // D2: mount unit (the handler + // passes its own; see handler.c) let port = cpu.dar[9]; // A1 let dp_type = bus.read_long(pkt + 8) as i32; let mut guest_op = None; - let (res1, res2) = self.handle_packet(bus, port, pkt, &mut guest_op); - let unit = self.ports.get(&port).copied(); + let (res1, res2) = self.handle_packet(bus, unit, port, pkt, &mut guest_op); log::debug!( "filesys: {}: packet type {dp_type} at {pkt:#010X} -> \ res1={res1:#X} res2={res2}", - unit.map_or("?".into(), device_name), + device_name(unit), ); bus.write_long(pkt + 12, res1); // dp_Res1 bus.write_long(pkt + 16, res2); // dp_Res2 @@ -1100,25 +1200,22 @@ mod tests { // A lock on Libs, as DOS supplies with opens through the LIBS: // assign. The name still carries the user's "LIBS:" prefix; it // must be stripped without resetting to the root. - hle.locks.insert( - 0x1000, - LockRec { - unit: 0, - rel: "Libs".into(), - }, - ); - let rec = hle.resolve(0, 0x1000 >> 2, b"LIBS:68040.library").unwrap(); + hle.units[0] + .locks + .insert(0x1000, LockRec { rel: "Libs".into() }); + let unit = &hle.units[0]; + let rec = unit.resolve(0x1000 >> 2, b"LIBS:68040.library").unwrap(); assert_eq!(rec.rel, PathBuf::from("Libs/68040.library")); // A volume prefix with no lock starts at the root as before. - let rec = hle.resolve(0, 0, b"Test:Libs/68040.library").unwrap(); + let rec = unit.resolve(0, b"Test:Libs/68040.library").unwrap(); assert_eq!(rec.rel, PathBuf::from("Libs/68040.library")); // Host dot-dirs must not act as path components: ".." would // escape the mount root ("." and ".." are legal-ish AmigaDOS // names with no special meaning; "/" is the parent). - assert!(hle.resolve(0, 0, b"..").is_none()); - assert!(hle.resolve(0, 0, b"Libs/../../etc").is_none()); - assert!(hle.resolve(0, 0, b"Libs/.").is_none()); + assert!(unit.resolve(0, b"..").is_none()); + assert!(unit.resolve(0, b"Libs/../../etc").is_none()); + assert!(unit.resolve(0, b"Libs/.").is_none()); std::fs::remove_dir_all(&root).unwrap(); }