aboutsummaryrefslogtreecommitdiff
path: root/chudisk.c
diff options
context:
space:
mode:
authorJavier <dev.git@javispedro.com>2026-09-29 23:40:31 +0200
committerJavier <dev.git@javispedro.com>2026-09-29 23:40:31 +0200
commit0a656a4a84f920194958d339d61ed567eba9c03c (patch)
tree0feecbddf0150e02fe7a2fa619dc232c50ced4c8 /chudisk.c
downloadchu-0a656a4a84f920194958d339d61ed567eba9c03c.tar.gz
chu-0a656a4a84f920194958d339d61ed567eba9c03c.zip
initial import
Diffstat (limited to 'chudisk.c')
-rw-r--r--chudisk.c896
1 files changed, 896 insertions, 0 deletions
diff --git a/chudisk.c b/chudisk.c
new file mode 100644
index 0000000..24de25e
--- /dev/null
+++ b/chudisk.c
@@ -0,0 +1,896 @@
+/*
+ * CHU - driver for CH375 in disk mode
+ * Copyright (C) 2024 Javier S. Pedro
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#include <i86.h>
+#include <string.h>
+
+#include "inc/dosdrv.h"
+#include "inc/bda.h"
+#include "inc/mbr.h"
+
+#include "chudisk.h"
+#include "ch375.h"
+#include "dlog.h"
+#include "utils.h"
+
+// Pre-declarations for the two driver routines, note there is a pragma aux for them
+DOS_DD_STRATEGY strategy_routine;
+DOS_DD_INTERRUPT interrupt_routine;
+
+/** This is the actual .SYS header! */
+const DOS_DD_HEADER __based(__segname("_CODE")) header = {
+ MK_FP(-1, -1),
+ DOS_DD_BLOCK | DOS_BLOCK_SUPPORT_32BIT_ADDRESSES
+ | DOS_BLOCK_FORMAT_NON_IBM | DOS_BLOCK_SUPPORT_REMOVABLE_MEDIA
+ | DOS_DD_SUPPORT_IOCTL_RW
+ | DOS_BLOCK_SUPPORT_IOCTL_GENERIC | DOS_DD_SUPPORT_IOCTL_QUERY,
+ strategy_routine,
+ interrupt_routine,
+ "\1CHUDISK"
+};
+
+/** Strategy routine saves DRS here for interrupt routine to use */
+DOS_DRS far * saved_drs;
+
+// Some settings
+/** The ch375 io port base. */
+unsigned short ch375_port;
+/** Number of units handled by the driver (i.e. number of drive letters reserved. ) */
+unsigned char num_units;
+/** Read-only mode (as if write-protected floppy) */
+bool read_only;
+/** Process FAT32 partitions too. */
+bool show_fat32;
+
+/* Runtime data for the driver. */
+struct chudisk_data data;
+
+/** Buffer used to load boot sectors from disk and partitions. */
+char buffer[512];
+
+/** Waits for the CH375 to generate an interrupt, returns result value.
+ * @param max_ticks max BIOS ticks to wait for interrupt. */
+uint8_t wait_for_ch375_result(unsigned int max_ticks)
+{
+ uint16_t last = bda_get_tick_count_lo();
+ while (!ch375_poll_interrupt() && max_ticks > 0) {
+ uint16_t now = bda_get_tick_count_lo();
+ if (now != last) {
+ last = now;
+ max_ticks--;
+ }
+ }
+ if (ch375_poll_interrupt()) {
+ uint8_t status = ch375_get_int_status();
+ //("CH375 INT status=0x%x\n", status);
+ return status;
+ } else {
+ dprintf("CH375 int timeout\n");
+ return 0;
+ }
+}
+
+/** Gets the disk sector from RW command DRS. */
+static inline uint32_t get_start_sector(const DOS_DRS far * drs)
+{
+ return (drs->cmd.rw.start_sector == 0xFFFF
+ ? drs->cmd.rw.start_sector_huge
+ : drs->cmd.rw.start_sector);
+}
+
+static inline uint32_t chs_to_lba(const DOS_BPB * bpb, uint16_t cylinder, uint16_t head, uint16_t sector)
+{
+ uint16_t track = cylinder * bpb->numberOfHeads + head;
+ return mul(track, bpb->sectorsPerTrack) + (sector - 1);
+}
+
+/** Read N sectors from the disk
+ * @return number of sectors read
+ */
+static unsigned disk_read_sectors(uint8_t far *buffer, uint32_t lba, uint8_t num)
+{
+ uint8_t result;
+ unsigned total = 0; // Total bytes written
+
+ dprintf("disk read buf=%Fp sector=%lu num=%u\n", buffer, lba, num);
+
+ // Initiate read operation
+ ch375_disk_read(lba, num);
+
+ // Now keep going as long as ch375 says to keep going
+ while ((result = wait_for_ch375_result(10)) == CH375_INT_USB_DISK_READ) {
+ unsigned len;
+
+ // Sanity check
+ if (total / SECTOR_SIZE >= num) {
+ dputs("read overflow");
+ break;
+ }
+
+ ch375_send_command(CH375_RD_USB_DATA);
+ len = ch375_read_data_block(&buffer[total]);
+
+ //dprintf(" disk read data block len = %d\n", len);
+
+ if (len == 0) {
+ // Done?
+ break;
+ }
+
+ total += len;
+
+ // Continue
+ ch375_send_command(CH375_DISK_RD_GO);
+ }
+
+ dprintf(" disk read total bytes = %u, sectors = %u, final status = 0x%hx\n",
+ total, total / SECTOR_SIZE, result);
+
+ return total / SECTOR_SIZE;
+}
+
+/** Write N sectors to the disk
+ * @return number of sectors written
+ */
+static unsigned disk_write_sectors(uint8_t far *buffer, uint32_t lba, uint8_t num)
+{
+ uint8_t result;
+ unsigned total = 0; // Total bytes written
+
+ dprintf("disk write buf=%Fp sector=%lu num=%u\n", buffer, lba, num);
+
+ // Initiate operation
+ ch375_disk_write(lba, num);
+
+ // Now keep going as long as ch375 says to keep going
+ while ((result = wait_for_ch375_result(10)) == CH375_INT_USB_DISK_WRITE) {
+ const unsigned len = 64;
+
+ // Sanity check
+ if (total / SECTOR_SIZE >= num) {
+ dputs("write overflow");
+ break;
+ }
+
+ ch375_send_command(CH375_WR_USB_DATA7);
+ ch375_write_data_block(&buffer[total], len);
+
+ total += len;
+
+ // Continue
+ ch375_send_command(CH375_DISK_WR_GO);
+ }
+
+ dprintf(" disk write total bytes = %u, sectors = %u, final status = 0x%hx\n",
+ total, total / SECTOR_SIZE, result);
+
+ return total / SECTOR_SIZE;
+}
+
+/** Enables CH375 USB host mode, initializes "disk" mode on it
+ * and connects to a disk. */
+static bool disk_connect()
+{
+ int tries;
+
+ dputs("switch to usb host");
+ ch375_set_usb_mode(CH375_USB_MODE_HOST_AUTO_SOF);
+ if (wait_for_ch375_result(4) != CH375_INT_USB_CONNECT) {
+ dputs("could not turn on usb host");
+ return false;
+ }
+
+ dputs("try init disk");
+ ch375_disk_init();
+ if (wait_for_ch375_result(20) != CH375_INT_USB_SUCCESS) {
+ dputs("could not init disk");
+ return false;
+ }
+
+ // looks like we have to keep polling by asking "is the disk ready?"
+ // even if each time we ask we are already polling
+ tries = 3;
+ while (tries-- > 0) {
+ ch375_test_disk_ready();
+ if (wait_for_ch375_result(20) == CH375_INT_USB_SUCCESS) {
+ dputs("disk now init");
+ return true;
+ }
+ bda_wait_tick();
+ }
+
+ dputs("disk failed to init");
+ return false;
+}
+
+/** Scans the disk for partitions. */
+static int disk_scan()
+{
+ uint32_t next_sector;
+ int num_parts, i;
+
+ dprintf("disk scan show_fat32=%d\n", show_fat32);
+
+ // Clear current partitions
+ for (i = 0; i < num_units; ++i) {
+ data.unit[i].exists = false;
+ data.unit[i].ready = false;
+ data.unit[i].start_sector = 0;
+ data.unit[i].sectors = 0;
+ }
+
+ // Refresh disk size
+ ch375_send_command(CH375_DISK_SIZE);
+ if (wait_for_ch375_result(4) != CH375_INT_USB_SUCCESS) {
+ return DOS_DD_ERR_MEDIA_UNAVAILABLE;
+ }
+
+ ch375_send_command(CH375_RD_USB_DATA);
+ if (ch375_read_data_block(buffer) < 8) {
+ dprintf("disk size command return less than 8 bytes\n");
+ return DOS_DD_ERR_UNKNOWN_MEDIA;
+ }
+
+ data.sectors = SWAPNC_32(*(uint32_t*)&buffer[0]);
+ data.bytesPerSector = SWAPNC_32(*(uint32_t*)&buffer[4]);
+
+ dprintf("sectors %lu bytesPerSector %lu\n", data.sectors, data.bytesPerSector);
+ if (data.bytesPerSector != SECTOR_SIZE) {
+ return DOS_DD_ERR_UNKNOWN_MEDIA;
+ }
+
+ // Start creating partitions/units from partition tables
+ num_parts = 0;
+
+ // Start with MBR at sector 0
+ next_sector = 0;
+ do {
+ const MBR_PARTENTRY *partentry;
+ const uint32_t this_sector = next_sector;
+
+ dprintf("reading boot record at sector %lu\n", this_sector);
+
+ // Try to read sector
+ if (disk_read_sectors(buffer, this_sector, 1) != 1) {
+ // Fatal for MBR, non-fatal for EBR
+ if (this_sector == 0) return DOS_DD_ERR_READ_FAULT;
+ break;
+ }
+
+ // Check for signature
+ if (*(uint16_t*)&buffer[510] != 0xAA55U) {
+ dprintf(" invalid MBR signature %x\n", *(uint16_t*)&buffer[510]);
+ if (this_sector == 0) return DOS_DD_ERR_UNKNOWN_MEDIA;
+ break;
+ }
+
+ // Parse the MBR
+ partentry = (MBR_PARTENTRY*)&buffer[446];
+
+ next_sector = 0; // If there is another EBR we will store it here
+ for (i = 0; i < 4 && num_parts < MAX_UNITS; i++) {
+ dprintf(" partition %d type %x start %lu len %lu\n", i, partentry[i].type, partentry[i].lbaFirst, partentry[i].lbaLen);
+
+ if (partentry[i].type == MBR_FAT12 || partentry[i].type == MBR_FAT16_32MB
+ || partentry[i].type == MBR_FAT16 || partentry[i].type == MBR_FAT16_LBA
+ || (show_fat32 && (partentry[i].type == MBR_FAT32 || partentry[i].type == MBR_FAT32_LBA)) ) {
+ data.unit[num_parts].exists = true;
+ data.unit[num_parts].start_sector = this_sector + partentry[i].lbaFirst;
+ data.unit[num_parts].sectors = partentry[i].lbaLen;
+ dprintf(" added as unit %u starts at %lu\n", num_parts, data.unit[num_parts].start_sector);
+ num_parts++;
+ } else if (partentry[i].type == MBR_EXT || partentry[i].type == MBR_EXT_LBA) {
+ // Go to the EBR next
+ next_sector = this_sector + partentry[i].lbaFirst;
+ }
+ }
+ } while (next_sector != 0 && num_parts < MAX_UNITS);
+
+ // OK!
+ dprintf("found %u partitions\n", num_parts);
+ data.ready = true;
+
+ return 0;
+}
+
+static int disk_init()
+{
+ ch375_test_disk_ready();
+ if (wait_for_ch375_result(4) != CH375_INT_USB_SUCCESS) {
+ // Disk is not even connected, try to connect
+ if (!disk_connect()) {
+ // Nothing works: fail
+ return DOS_DD_ERR_DRIVE_NOT_READY;
+ }
+ }
+ // Disk is now ready: initiate scan for partitions
+ return disk_scan();
+}
+
+/** Return the volume label from the cached BPB, i.e. from the last time the drive was accessed. */
+static inline const char * unit_get_volume_label(uint8_t unit)
+{
+ const DOS_BPB *bpb = &data.unit[unit].bpb;
+
+ if (bpb->sectorsPerFAT != 0) {
+ // FAT16
+ return bpb->fat16.volumeLabel;
+ } else {
+ // FAT32
+ return bpb->fat32.volumeLabel;
+ }
+}
+
+/** Try to initialize logigcal drive 'unit'. This also initializes the entire disk
+ * if it was not done yet (scanning all partitions).
+ * It will read the BPB of the corresponding partition and cache it. */
+static int unit_init(uint8_t unit)
+{
+ DOS_BPB *bpb = &data.unit[unit].bpb;
+ int err;
+
+ // Check if disk is ready, and initialize it if not
+ if (!data.ready) {
+ if (err = disk_init()) {
+ return err;
+ }
+ }
+
+ // Disk should be ready, partitions enumerated
+ if (!data.unit[saved_drs->unit].exists) {
+ dputs("disk is ready, but partition does not exist");
+ return DOS_DD_ERR_MEDIA_UNAVAILABLE;
+ }
+
+ // Try to read starting sector of partition into buffer
+ if (disk_read_sectors(buffer, data.unit[unit].start_sector, 1) != 1) {
+ // Cannot read start sector...
+ return DOS_DD_ERR_READ_FAULT;
+ }
+
+ // For now, copy largest possible BPB into our internal cache
+ memcpy(bpb, &buffer[DOS_BPB_OFFSET_ON_DISK], sizeof(DOS_BPB));
+
+ dprintf("unit %u BPB sectorsPerCluster=%u sectorsPerTrack=%u numberOfHeads=%u\n",
+ unit, bpb->sectorsPerCluster,
+ bpb->sectorsPerTrack, bpb->numberOfHeads);
+ dprintf(" bytesPerSector=%u numberOfFats=%u sectorsPerFat=%u \n",
+ bpb->bytesPerSector,bpb->numberOfFATs,
+ bpb->sectorsPerFAT);
+ dprintf(" numberOfSectors=%u numberOfSectorsLarge=%lu\n",
+ bpb->numberOfSectors, bpb->numberOfSectorsLarge);
+
+ if (bpb->sectorsPerFAT != 0) {
+ // FAT12/16
+ dprintf(" %s volumeSerialNumber=%lx volumeLabel='%.11s' fsType='%.8s'\n",
+ "FAT16", bpb->fat16.volumeSerialNumber, bpb->fat16.volumeLabel, bpb->fat16.fsType);
+ } else if (bpb->fat32.sectorPerFatLarge != 0) {
+ dprintf(" %s volumeSerialNumber=%lx volumeLabel='%.11s' fsType='%.8s'\n",
+ "FAT32", bpb->fat32.volumeSerialNumber, bpb->fat32.volumeLabel, bpb->fat32.fsType);
+ } else {
+ dprintf(" -> unknown filesystem\n");
+ return DOS_DD_ERR_UNKNOWN_MEDIA;
+ }
+
+ data.unit[saved_drs->unit].ready = true;
+ return 0;
+}
+
+static bool unit_check_media_changed(uint8_t unit)
+{
+ if (data.ready) {
+ // Recheck if disk is still ready
+ ch375_test_disk_ready();
+ if (wait_for_ch375_result(4) == CH375_INT_USB_SUCCESS) {
+ // Disk is ready. But is partition ready ?
+ if (data.unit[unit].ready) {
+ // Disk ready, partition ready. Perfect!
+ dputs("mediaquery -> disk ready -> unit ready -> not changed");
+ return false;
+ } else {
+ // Disk ready, but this partition is not.
+ // Likely first time access for this partition.
+ dputs("mediaquery -> disk ready -> unit !ready -> changed");
+ return true;
+ }
+ } else {
+ // CH375 is not ready but we thought it was. USB has been removed!
+ dputs("mediaquery -> disk not ready -> changed");
+ // Invalidate disk ready flag so that we rescan for partitions
+ data.ready = false;
+ return true;
+ }
+ } else {
+ // We do not think CH375 is ready. First time we check, or USB not present.
+ dputs("mediaquery -> !ready -> changed");
+ return true;
+ }
+}
+
+void strategy_routine(DOS_DRS __far *drs)
+{
+ load_ds_from_cs();
+ //dprintf("strategy routine drs=%Fp len=%u func=%u\n", drs, drs->len, drs->function);
+ saved_drs = drs;
+}
+
+void interrupt_routine()
+{
+ int err;
+ load_ds_from_cs();
+ //dprintf("interrupt routine drs=%Fp len=%u func=%u\n", saved_drs, saved_drs->len, saved_drs->function);
+ switch (saved_drs->function) {
+ case DOS_DD_FUNC_INIT:
+ driver_init(saved_drs);
+ break;
+ case DOS_DD_FUNC_MEDIA_CHECK:
+ if (saved_drs->unit >= num_units) {
+ saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ saved_drs->cmd.mediacheck.out.result = unit_check_media_changed(saved_drs->unit)
+ ? DOS_DD_MEDIAQUERY_CHANGED : DOS_DD_MEDIAQUERY_NOT_CHANGED;
+ saved_drs->cmd.mediacheck.out.volume_id = unit_get_volume_label(saved_drs->unit);
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ break;
+ case DOS_DD_FUNC_BUILD_BPB:
+ dprintf("build bpb unit=%hu\n", saved_drs->unit);
+ if (saved_drs->unit >= num_units) {
+ dputs("unit is unknown");
+ saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ // Try to initialize disk and unit
+ if (err = unit_init(saved_drs->unit)) {
+ saved_drs->status = err | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ // Return pointer to our cached BPB
+ saved_drs->cmd.buildbpb.out.bpb = &data.unit[saved_drs->unit].bpb;
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ break;
+ case DOS_DD_FUNC_READ:
+ dprintf("read unit=%hu sector=%u large_sector=%lu len=%u\n", saved_drs->unit,
+ saved_drs->cmd.rw.start_sector, saved_drs->cmd.rw.start_sector_huge, saved_drs->cmd.rw.len);
+ if (saved_drs->unit >= num_units) {
+ saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ if (!data.ready || !data.unit[saved_drs->unit].ready) {
+ dputs("partition not ready");
+ // We don't try to initialize the drive here, DOS must do that by calling buildBPB
+ saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ if (saved_drs->cmd.rw.len > 128) {
+ // Too many sectors at once!
+ // TODO: Should we allow partial reads? When I tried even "type <file>" crashes.
+ // TODO: Should we also check for segment wraparound?
+ saved_drs->cmd.rw.len = 0;
+ saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ } else if (saved_drs->cmd.rw.len >= 1) {
+ uint32_t sector = get_start_sector(saved_drs);
+ if (sector > data.unit[saved_drs->unit].sectors) {
+ saved_drs->cmd.rw.len = 0;
+ saved_drs->status = DOS_DD_ERR_SECTOR_NOT_FOUND | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ // Make absolute
+ sector += data.unit[saved_drs->unit].start_sector;
+ if (sector > data.sectors) {
+ saved_drs->cmd.rw.len = 0;
+ saved_drs->status = DOS_DD_ERR_SEEK | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+
+ saved_drs->cmd.rw.len = disk_read_sectors(saved_drs->cmd.rw.buffer, sector, saved_drs->cmd.rw.len);
+ if (saved_drs->cmd.rw.len == 0) {
+ // Not sure if required
+ saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ }
+ saved_drs->cmd.rw.volume_id = unit_get_volume_label(saved_drs->unit);
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ break;
+ case DOS_DD_FUNC_WRITE:
+ case DOS_DD_FUNC_WRITE_VERIFY: // TODO Not doing the verify yet part...
+ dprintf("write unit=%hu sector=%u large_sector=%lu len=%u\n", saved_drs->unit,
+ saved_drs->cmd.rw.start_sector, saved_drs->cmd.rw.start_sector_huge, saved_drs->cmd.rw.len);
+ if (saved_drs->unit >= num_units) {
+ saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ if (!data.ready || !data.unit[saved_drs->unit].ready) {
+ dputs("partition not ready");
+ saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ if (read_only) {
+ saved_drs->status = DOS_DD_ERR_WRITE_PROTECT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ if (saved_drs->cmd.rw.len > 128) {
+ // Too much!
+ saved_drs->cmd.rw.len = 0;
+ saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ } else if (saved_drs->cmd.rw.len >= 1) {
+ // Check relative sector value against partition limit
+ uint32_t sector = get_start_sector(saved_drs);
+ if (sector > data.unit[saved_drs->unit].sectors) {
+ saved_drs->cmd.rw.len = 0;
+ saved_drs->status = DOS_DD_ERR_SECTOR_NOT_FOUND | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ // Make absolute and check against disk limit
+ sector += data.unit[saved_drs->unit].start_sector;
+ if (sector > data.sectors) {
+ saved_drs->cmd.rw.len = 0;
+ saved_drs->status = DOS_DD_ERR_SEEK | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+
+ saved_drs->cmd.rw.len = disk_write_sectors(saved_drs->cmd.rw.buffer, sector, saved_drs->cmd.rw.len);
+ if (saved_drs->cmd.rw.len == 0) {
+ // Not sure if required
+ saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+
+ if (saved_drs->function == DOS_DD_FUNC_WRITE_VERIFY) {
+ // Verify one sector at a time. Use our internal buffer.
+ uint8_t far * origbuf = saved_drs->cmd.rw.buffer;
+ unsigned i;
+ for (i = 0; i < saved_drs->cmd.rw.len; i++) {
+ dprintf(" verifying sector %u\n", i);
+ if (disk_read_sectors(buffer, sector + i, 1) != 1) {
+ saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+
+ if (_fmemcmp(&origbuf[i * SECTOR_SIZE], buffer, SECTOR_SIZE) != 0) {
+ dprintf(" cmp error for sec %u\n", i);
+ saved_drs->status = DOS_DD_ERR_CRC | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ }
+ }
+ }
+ saved_drs->cmd.rw.volume_id = unit_get_volume_label(saved_drs->unit);
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ break;
+ case DOS_DD_FUNC_DEVICE_OPEN:
+ dputs("device open");
+ // Do nothing here -- we init the device on build bpb, as with older DOSes
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ break;
+ case DOS_DD_FUNC_DEVICE_CLOSE:
+ dputs("device close");
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ break;
+ case DOS_DD_FUNC_REMOVEABLE_MEDIA:
+ dputs("removable device");
+ // If the disk is fixed, we must set "busy" bit here. We are removable.
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ break;
+ case DOS_DD_FUNC_IOCTL_READ:
+ dprintf("ioctl read unit=%hu buf=%Fp len=%u\n", saved_drs->unit,
+ saved_drs->cmd.ioctlrw.buffer, saved_drs->cmd.ioctlrw.len);
+ if (saved_drs->unit >= num_units) {
+ saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ // If there's enough space, return our internal config string.
+ if (saved_drs->cmd.ioctlrw.len >= 3 + 2 + 1 + 5 + 1) {
+ uint8_t far * buf = saved_drs->cmd.ioctlrw.buffer;
+ int len = 0;
+ // B = base port (2 byte)
+ buf[len++] = 'B';
+ _fmemcpy(&buf[len], &ch375_port, sizeof(ch375_port));
+ len += sizeof(ch375_port);
+ // 3 = showing FAT32 (1 byte)
+ buf[len++] = '3';
+ buf[len++] = show_fat32;
+ // R = read only (1 byte)
+ buf[len++] = 'R';
+ buf[len++] = read_only;
+ // S = partition start (4 bytes)
+ buf[len++] = 'S';
+ _fmemcpy(&buf[len], &data.unit[saved_drs->unit].start_sector, sizeof(uint32_t));
+ len += sizeof(uint32_t);
+ buf[len++] = '\0';
+ saved_drs->cmd.ioctlrw.len = len;
+ } else {
+ saved_drs->cmd.ioctlrw.len = 0;
+ }
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ break;
+ case DOS_DD_FUNC_IOCTL_WRITE:
+ dprintf("ioctl write unit=%hu buf=%Fp len=%u\n", saved_drs->unit,
+ saved_drs->cmd.ioctlrw.buffer, saved_drs->cmd.ioctlrw.len);
+ if (saved_drs->unit >= num_units) {
+ saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ if (saved_drs->cmd.ioctlrw.len >= 2) {
+ uint8_t far * buf = saved_drs->cmd.ioctlrw.buffer;
+ int i = 0;
+ do {
+ char c = buf[i++];
+ switch (c) {
+ case 'S': // Partition start (5 bytes)
+ _fmemcpy(&data.unit[saved_drs->unit].start_sector, &buf[i], sizeof(uint32_t));
+ i += sizeof(uint32_t);
+ dprintf(" unit=%hu new start=%lu\n", saved_drs->unit, data.unit[saved_drs->unit].start_sector);
+ // Mark unit as valid but needing refresh
+ data.unit[saved_drs->unit].exists = true;
+ data.unit[saved_drs->unit].ready = false;
+ break;
+ case 'L': // Partition len (5 bytes)
+ _fmemcpy(&data.unit[saved_drs->unit].sectors, &buf[i], sizeof(uint32_t));
+ i += sizeof(uint32_t);
+ dprintf(" unit=%hu new len=%lu\n", saved_drs->unit, data.unit[saved_drs->unit].sectors);
+ // Mark unit as valid but needing refresh
+ data.unit[saved_drs->unit].exists = true;
+ data.unit[saved_drs->unit].ready = false;
+ break;
+ case '\0':
+ break;
+ default:
+ dprintf(" invalid control string char '%c' at %d\n", c, i-1);
+ saved_drs->cmd.ioctlrw.len = i;
+ saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ } while (i < saved_drs->cmd.ioctlrw.len);
+ } else {
+ saved_drs->cmd.ioctlrw.len = 0;
+ saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ break;
+ case DOS_DD_FUNC_IOCTL_GENERIC:
+ dprintf("ioctl unit=%hu %x:%x buf=%Fp\n", saved_drs->unit,
+ saved_drs->cmd.ioctl.category, saved_drs->cmd.ioctl.minor, saved_drs->cmd.ioctl.data);
+ if (saved_drs->unit >= num_units) {
+ saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ // DOS can call these functions without doing a mediacheck/buildBPB
+ // Do not want to "forget" about a media change, so can't just call buildBPB here.
+ // But some functions (like get device parameters?) seem to act like a buildBPB.
+ if (unit_check_media_changed(saved_drs->unit)) {
+ // Init the disk, but not the partition, until we are sure we want to.
+ if (!data.ready) {
+ if (err = disk_init()) {
+ // Cannot access to disk or scan for partitions? No IOCTLs for you!
+ saved_drs->status = err | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ }
+ }
+ if (saved_drs->cmd.ioctl.category == DOS_IOCTL_DISK) {
+ DOS_IOCTL_DISK_DATA far * iod = saved_drs->cmd.ioctl.data;
+ CHUDISK_IOCTL_DISK_DATA far * ciod = saved_drs->cmd.ioctl.data;
+ DOS_BPB * bpb = &data.unit[saved_drs->unit].bpb;
+
+ switch (saved_drs->cmd.ioctl.minor) {
+ case DOS_IOCTL_DISK_GET_DEVICE_PARAMETERS: // Get CHS values (full disk!) and BPB
+ dprintf(" get_device_parameters specialFunctions=0x%x\n", iod->devparams.specialFunctions);
+ // If partition's BPB is not cached, read it from the disk
+ if (!data.unit[saved_drs->unit].ready) {
+ // This acts as if buildBPB was called
+ if ((err = unit_init(saved_drs->unit))) {
+ // TODO Now would be a good time to talk about a default BPB...
+ saved_drs->status = err | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ }
+
+ iod->devparams.specialFunctions = DOS_DEVICEPARAMS_USE_DEVICE_BPB | DOS_DEVICEPARAMS_REGULARTRACKS;
+ iod->devparams.deviceType = DOS_DEVICEPARAMS_HDD;
+ iod->devparams.deviceAttributes = DOS_DEVICEPARAMS_REMOVABLE;
+ if (bpb->numberOfHeads && bpb->sectorsPerTrack) {
+ // Compute number of cylinders of entire disk based on this part's BPB...
+ iod->devparams.numberOfCylinders =
+ div(data.sectors, bpb->numberOfHeads * bpb->sectorsPerTrack);
+ // TODO Check for overflows...
+ } else {
+ iod->devparams.numberOfCylinders = 0;
+ }
+ iod->devparams.mediaType = 0;
+
+ // Return only DOS3.x BPB -- not sure if caller will have space for anything larger.
+ _fmemcpy(&iod->devparams.bpb, bpb, sizeof(DOS_BPB_DOS3));
+
+ dprintf(" get_device_parameters -> numberOfCylinders=%u\n", iod->devparams.numberOfCylinders);
+
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ return;
+ case DOS_IOCTL_DISK_GET_MEDIA_ID: // Get the volume label
+ if (!data.unit[saved_drs->unit].ready) {
+ // TODO Should we reload BPB here like above?
+ dputs(" but partition is not ready");
+ saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ iod->mid.infoLevel = 0;
+ if (bpb->sectorsPerFAT != 0) {
+ iod->mid.serialNumber = bpb->fat16.volumeSerialNumber;
+ _fmemcpy(iod->mid.volumeLabel, bpb->fat16.volumeLabel, 11);
+ _fmemcpy(iod->mid.fsType, bpb->fat16.fsType, 8);
+ } else if (bpb->fat32.sectorPerFatLarge != 0) {
+ iod->mid.serialNumber = bpb->fat32.volumeSerialNumber;
+ _fmemcpy(iod->mid.volumeLabel, bpb->fat32.volumeLabel, 11);
+ _fmemcpy(iod->mid.fsType, bpb->fat32.fsType, 8);
+ }
+ dprintf(" get media id -> volumeSerialNumber=%lx volumeLabel='%.11Fs' fsType='%.8Fs'\n",
+ iod->mid.serialNumber, iod->mid.volumeLabel, iod->mid.fsType);
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ return;
+ case DOS_IOCTL_DISK_SET_MEDIA_ID: // Set the volume label, etc.
+ if (!data.unit[saved_drs->unit].exists) {
+ dputs(" but partition does not exist!");
+ saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ dprintf(" set media id to volumeSerialNumber=%lx volumeLabel='%.11Fs' fsType='%.8Fs'\n",
+ iod->mid.serialNumber, iod->mid.volumeLabel, iod->mid.fsType);
+ if (read_only) {
+ saved_drs->status = DOS_DD_ERR_WRITE_PROTECT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ // Here we always reload BPB -- do not want to write back outdated stuff.
+ if (disk_read_sectors(buffer, data.unit[saved_drs->unit].start_sector, 1) != 1) {
+ // Cannot read start sector...
+ saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ bpb = (DOS_BPB*) &buffer[DOS_BPB_OFFSET_ON_DISK];
+ if (bpb->sectorsPerFAT != 0) {
+ bpb->fat16.volumeSerialNumber = iod->mid.serialNumber;
+ _fmemcpy(bpb->fat16.volumeLabel, iod->mid.volumeLabel, 11);
+ _fmemcpy(bpb->fat16.fsType, iod->mid.fsType, 8);
+ } else if (bpb->fat32.sectorPerFatLarge != 0) {
+ bpb->fat32.volumeSerialNumber = iod->mid.serialNumber;
+ _fmemcpy(bpb->fat32.volumeLabel, iod->mid.volumeLabel, 11);
+ _fmemcpy(bpb->fat32.fsType, iod->mid.fsType, 8);
+ } else {
+ saved_drs->status = DOS_DD_ERR_UNKNOWN_MEDIA | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ if (disk_write_sectors(buffer, data.unit[saved_drs->unit].start_sector, 1) != 1) {
+ // Cannot re-write start sector...
+ saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ // Re-sync our internal BPB
+ memcpy(&data.unit[saved_drs->unit].bpb, bpb, sizeof(DOS_BPB));
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ return;
+ case DOS_IOCTL_DISK_READ_TRACK_ON_LOGICAL_DEVICE: // Absolute read
+ dprintf(" read track cylinder %u head %u sector %u sectors %u buf %Fp -> lba %lu\n",
+ iod->rw.cylinder, iod->rw.head, iod->rw.firstSector, iod->rw.sectors, iod->rw.buffer,
+ chs_to_lba(bpb, iod->rw.cylinder, iod->rw.head, iod->rw.firstSector + 1));
+ // Need the partition's BPB to map CHS to LBA...
+ if (!data.unit[saved_drs->unit].ready) {
+ saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ if (disk_read_sectors(iod->rw.buffer, chs_to_lba(bpb, iod->rw.cylinder, iod->rw.head, iod->rw.firstSector + 1), iod->rw.sectors) != iod->rw.sectors) {
+ saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ return;
+ case DOS_IOCTL_DISK_WRITE_TRACK_ON_LOGICAL_DEVICE:
+ dprintf(" write track cylinder %u head %u sector %u sectors %u buf %Fp -> lba %lu\n",
+ iod->rw.cylinder, iod->rw.head, iod->rw.firstSector, iod->rw.sectors, iod->rw.buffer,
+ chs_to_lba(bpb, iod->rw.cylinder, iod->rw.head, iod->rw.firstSector + 1));
+ // Need the partition's BPB to map CHS to LBA...
+ if (!data.unit[saved_drs->unit].ready) {
+ saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ if (read_only) {
+ saved_drs->status = DOS_DD_ERR_WRITE_PROTECT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ if (disk_write_sectors(iod->rw.buffer, chs_to_lba(bpb, iod->rw.cylinder, iod->rw.head, iod->rw.firstSector + 1), iod->rw.sectors) != iod->rw.sectors) {
+ saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ return;
+ case DOS_IOCTL_DISK_FORMAT_TRACK_ON_LOGICAL_DEVICE:
+ dprintf(" formatting track cylinder %u head %u\n",
+ iod->fv.cylinder, iod->fv.head);
+ if (!data.unit[saved_drs->unit].exists) {
+ dputs(" but partition does not exist!");
+ saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ if (read_only) {
+ saved_drs->status = DOS_DD_ERR_WRITE_PROTECT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ // Total nop. Should we zerofill the track or something?
+ iod->fv.specialFunctions = 1; // Not supported by BIOS?
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ return;
+ case CHUDISK_IOCTL_DISK_READ_ABS_SECTOR:
+ dprintf(" read abs sector %lu sectors %u buf %Fp\n",
+ ciod->rw.sector, ciod->rw.sectors, ciod->rw.buffer);
+ if (disk_read_sectors(ciod->rwabs.buffer, ciod->rwabs.sector, ciod->rwabs.sectors) != ciod->rwabs.sectors) {
+ saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE;
+ return;
+ }
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ return;
+ }
+ }
+
+ dputs(" ioctl not supported\n");
+ saved_drs->status = DOS_DD_ERR_UNKNOWN_COMMAND | DOS_DD_STATUS_ERROR_DONE;
+ break;
+ case DOS_DD_FUNC_GET_LOGICAL_DEVICE:
+ dprintf("get logical device unit=%hu\n", saved_drs->unit);
+ saved_drs->unit = 0;
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ break;
+ case DOS_DD_FUNC_SET_LOGICAL_DEVICE:
+ // We do not support this. Ignore it.
+ dprintf("set logical device unit=%hu\n", saved_drs->unit);
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ break;
+ case DOS_DD_FUNC_IOCTL_QUERY:
+ dprintf("ioctl query %x:%x buf=%Fp\n", saved_drs->cmd.ioctl.category, saved_drs->cmd.ioctl.minor, saved_drs->cmd.ioctl.data);
+ switch (saved_drs->cmd.ioctl.category) {
+ case DOS_IOCTL_DISK:
+ switch (saved_drs->cmd.ioctl.minor) {
+ case DOS_IOCTL_DISK_GET_DEVICE_PARAMETERS:
+ case DOS_IOCTL_DISK_GET_MEDIA_ID:
+ case DOS_IOCTL_DISK_SET_MEDIA_ID:
+ case DOS_IOCTL_DISK_READ_TRACK_ON_LOGICAL_DEVICE:
+ case DOS_IOCTL_DISK_WRITE_TRACK_ON_LOGICAL_DEVICE:
+ case DOS_IOCTL_DISK_FORMAT_TRACK_ON_LOGICAL_DEVICE:
+ case CHUDISK_IOCTL_DISK_READ_ABS_SECTOR:
+ // Supported
+ saved_drs->status = DOS_DD_STATUS_DONE;
+ return;
+ }
+ break;
+ }
+ // Unsupported
+ saved_drs->status = DOS_DD_ERR_UNKNOWN_COMMAND | DOS_DD_STATUS_ERROR_DONE;
+ break;
+ default:
+ dprintf("unknown function received %u\n", saved_drs->function);
+ break;
+ }
+}
+
+// Required by Watcom ABI -- since we do not link to the runtime
+void _small_code() {}
+
+// Symbol to let initialization code know the end of resident data
+char resident_end;