/*
* 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 .
*/
#include
#include
#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 " 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;