aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJavier <dev.git@javispedro.com>2026-10-26 00:50:06 +0100
committerJavier <dev.git@javispedro.com>2026-10-26 00:50:06 +0100
commit6b8a368c51082fdd010ef5e1b0be4eb9b5059d1a (patch)
treee274d41a8dd5e912e20617a937a1fda5af91f8de
parent9b1cf601360fb194047e6dcc2af75c2f38c611ec (diff)
downloadchu-6b8a368c51082fdd010ef5e1b0be4eb9b5059d1a.tar.gz
chu-6b8a368c51082fdd010ef5e1b0be4eb9b5059d1a.zip
refactor common parts into inc/ch375.h and add win9x, linux drivers
-rw-r--r--.gitignore1
-rw-r--r--README.md146
-rw-r--r--dos/ch375dos.h103
-rw-r--r--dos/chud.c (renamed from chud.c)0
-rw-r--r--dos/chudinit.c (renamed from chudinit.c)25
-rw-r--r--dos/chudisk.c (renamed from chudisk.c)53
-rw-r--r--dos/chudisk.h (renamed from chudisk.h)7
-rw-r--r--dos/chudisk.lnk (renamed from chudisk.lnk)2
-rw-r--r--dos/cio.c (renamed from cio.c)0
-rw-r--r--dos/cio.h (renamed from cio.h)0
-rw-r--r--dos/inc/bda.h (renamed from inc/bda.h)0
-rw-r--r--dos/inc/bpb.h (renamed from inc/bpb.h)0
-rw-r--r--dos/inc/dosapi.h (renamed from inc/dosapi.h)0
-rw-r--r--dos/inc/dosdrv.h (renamed from inc/dosdrv.h)0
-rw-r--r--dos/inc/dosint.h (renamed from inc/dosint.h)0
-rw-r--r--dos/inc/mbr.h (renamed from inc/mbr.h)0
-rw-r--r--dos/makefile66
-rw-r--r--dos/utest.c (renamed from test.c)131
-rw-r--r--dos/utils.h (renamed from utils.h)0
-rw-r--r--inc/ch375.h (renamed from ch375.h)218
-rw-r--r--inc/chuver.h7
-rw-r--r--inc/dlog.h (renamed from dlog.h)102
-rw-r--r--inc/sassert.h2
-rw-r--r--inc/scsi.h61
-rw-r--r--inc/usb.h (renamed from usb.h)6
-rw-r--r--linux/ch375lnx.h76
-rw-r--r--linux/chudisk.c622
-rw-r--r--linux/lnxabs.h139
-rw-r--r--linux/makefile28
-rwxr-xr-xlinux/mkdev.sh19
-rw-r--r--makefile94
-rw-r--r--w9x/ch375vxd.h74
-rw-r--r--w9x/chudisk.inf45
-rw-r--r--w9x/chudisk.lnk8
-rw-r--r--w9x/chupdr.c816
-rw-r--r--w9x/dumpdcb.h110
-rw-r--r--w9x/inc/configmg.h213
-rw-r--r--w9x/inc/ios.h1520
-rw-r--r--w9x/inc/vmm.h161
-rw-r--r--w9x/inc/vxd.h132
-rw-r--r--w9x/makefile40
-rw-r--r--w9x/utils.h33
42 files changed, 4729 insertions, 331 deletions
diff --git a/.gitignore b/.gitignore
index e25cf3b..f42e1e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
*.com
*.sys
*.map
+*.pdr
diff --git a/README.md b/README.md
index 4cc5263..5c659ec 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,27 @@
-This is a DOS disk driver for the CH375 "USB ISA adapter", mostly designed with the Pocket386/Book386 in mind.
+This is a disk driver the CH375 "USB ISA adapter", mostly designed with the Pocket386/Book386 in mind.
+It allows you to use USB pendrives (and other mass storage devices, like USB HDDs)
+as if they were removable disks.
There is already a good number of drivers out there, but as far as I can see they are all derivatives of the original one,
and I got tired of all the obscure hangs and crashes. This driver is in OpenWatcom C and written from scratch
using the datasheets, so it has a (hopefully different) new repertoire of bugs to play with.
+The same abstraction (ch375.h) is used to provide drivers for multiple OSes
+that work on the Pocket386/Book386: DOS, Win9x, and early Linux (~ 2.0/2.2).
-Some features:
+Hot plug/unplug is also supported,
+but it will only be noticed the next time you access the drive.
+
+# Download
+
+Current version is 1.0 [📦 chu1_0.zip](https://depot.javispedro.com/dos/chu/chu1_0.zip).
+
+[Older versions are available here.](https://depot.javispedro.com/dos/chu/)
+
+[TOC]
+
+# DOS (MS-DOS, FreeDOS, etc.)
+
+Features on DOS platforms:
* *Can support multiple partitions.* You can either show multiple drive letters (the number of drive letters is fixed at boot, though)
and/or dynamically switch a single drive letter to a different partition from the command prompt.
@@ -20,7 +37,7 @@ If DOS asks for geometry, it gives whatever is on the BPB of your current partit
(only to re-format existing partitions, since it will simply re-use geometry from whatever FAT is already there).
Note this is a DOS-level driver, so FDISK won't work (which expects a BIOS/int13-level driver).
-* Build is for 386+, tested on pocket386/book386, but source builds on 8086.
+* Build is for 386+, tested on pocket386/book386, but the source builds on 8086 at least.
* Extensive (not included by default) verbose log over serial port.
@@ -29,21 +46,13 @@ any benchmark in speed or memory usage, but it gets around. About 400KB/s on the
I get with other drivers anyway; and around 7KiB of memory usage (the official driver sits at 4KiB).
Hopefully much easier to experiment with than with the official one.
-# Download
-
-[chu.zip](https://depot.javispedro.com/dos/chu/chu0.zip)
-Current version is 0.
-
-
-# Installation
+## Installation
````
DEVICE=CHUDISK.SYS [options]
````
-## CHUDISK.SYS configuration options
-
Current options:
* /P260 : IO port for CH375 (defaults to 0x260, as on the pocket386)
@@ -56,7 +65,7 @@ Each drive letter will by default be mapped to a different partition in order.
* /3 : show FAT32 partitions to the OS. Useful for OSes with native FAT32 support like FreeDOS.
-# Usage
+## Usage
The program CHUD.EXE can be used to switch a CHUDISK.SYS managed drive letter to a different partition.
E.g.
@@ -71,16 +80,121 @@ C:\> CHUD D:
Select partition in [1..3] range:
````
-Extended partitions, W95/LBA partitions are supported. Of course you can only select FAT16 partitions,
-and perhaps FAT32 ones if lucky (DOS7.1, FreeDOS, etc.) or forced with /3.
+Extended partitions, W95/LBA partitions are supported.
+Only FAT partitions can be selected.
+FAT32 partitions can also be selected on a DOS that supports them (DOS7.1, FreeDOS, etc.).
Note the drive letters <-> partitions mapping is not persistent, and will in fact be reset if you reboot
or disconnect the USB drive.
-# Building
+## Building
Tested with OpenWatcom 2, build from any OS where OpenWatcom runs.
````
wmake all
````
+
+If you add `debug=1`, then it will build debug versions which pass
+all dlog/dprintf statements to the first serial port (but they are very slow).
+
+# Windows 9x
+
+On Windows 9x, the driver is a IOS port driver, which makes it appear as
+a removable disk. Windows takes cares of parsing partition tables here.
+
+Note that because it is marked as removable, Windows disables caching,
+so launching stuff from the USB disk will be very slow.
+
+By default, Windows *assigns 1 drive letter to removable drives*, but if the
+drive is inserted before Windows starts, it will instead assign as many
+letters as you have partitions.
+You can also force Windows to assign more than one drive letter, or even
+decide which drive letters you prefer for the partitions on your pendrive.
+For that, go to Device Manager, Disk Drives, and select the CH375 USB Disk
+as follows:
+
+![Assigning Disk Drive Leters](https://depot.javispedro.com/dos/chu/config.png)
+
+Partitions will be assigned to disk letters in order.
+
+## Installation
+
+You have to follow the usual procedure for non-PnP hardware in Win 9x.
+
+1. Go to the Add New Hardware Wizard in Control Panel.
+
+2. Say you do not want to do automatic detection, and select "Hard Disk Controller"
+from the type of devices.
+
+![Add New Hardware Wizard](https://depot.javispedro.com/dos/chu/install1.png)
+
+3. Use "Have Disk..." and point to the directory containing `chudisk.pdr` and
+`chudisk.inf`.
+
+4. When asked, choose "CH375 USB Disk Controller"
+
+5. At this point Windows creates the device with some random default I/O port
+settings. Do not reboot yet.
+
+6. Go to Device Manager, choose the CH375 Disk Controller device (not the disk).
+In the Resources tab, you can configure the IO address to match whatever your
+hardware is using. On the Pocket386/Book386, this is 0x260-0x261.
+
+![Resources tab](https://depot.javispedro.com/dos/chu/install2.png)
+
+7. Windows will tell you that now you can shutdown the computer to install
+your hardware. It's already installed, so reboot.
+
+## Building
+
+Uses OpenWatcom, same as DOS one. With `debug=1` , by default the debug log
+goes into the IOS debug log, which will only be shown in the debug versions
+of IOS.
+
+# Linux 2.0-2.2
+
+No binaries are currently provided for Linux, since I find it easier to just
+build for your distribution. You can even do that on-device.
+
+I have tested this on Debian Potato, with the latest 2.0 and 2.2 kernels.
+Likely won't build on any other kernel version.
+
+## Installation / building
+
+Ensure that ``/lib/modules/`uname -r`/build`` exists, and that it points
+to the kernel headers.
+For Debian, this is usually a `kernel-headers-nnnn` package.
+Note you cannot put a generic kernel source dump there, as it will lack the
+usual headers generated during kernel build.
+
+````
+$ make
+(as root:)
+# make install
+# ./mkdev.sh
+(this will generate all /dev/chud* device files)
+````
+
+You can then use/mount the `/dev/chuda1`, `/dev/chuda2` devices as usual.
+
+## Usage
+
+The driver exposes the following parameters:
+
+* `io` the io address for your CH375, defaults to 0x260.
+* `major` allows overriding the device's major number, in the rare case you
+have a conflict. If you do this, also update `mkdev.sh`.
+* `force` will load the driver even if we can't find a CH375 at the specified
+address.
+* `softblk` allows setting the "block size" used for transfers.
+Linux defaults to 1024. Higher values provide higher speed at the cost
+of latency. Must be power of two.
+
+
+# Future work
+
+* Currently the driver is polling AND blocking in all OSes. Due to the
+relative fast speed of most pendrives, the driver spends most of its time
+doing PIO rather than waiting for the CH375, so I do not see the reason
+to change this. Also, on Pocket386/Book386, the IRQ line is not wired.
diff --git a/dos/ch375dos.h b/dos/ch375dos.h
new file mode 100644
index 0000000..d0ee84c
--- /dev/null
+++ b/dos/ch375dos.h
@@ -0,0 +1,103 @@
+/*
+ * CHU - IO routines to CH375 from DOS
+ * Copyright (C) 2024-2026 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/>.
+ */
+
+#ifndef CH375DOS_H
+#define CH375DOS_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <conio.h>
+#include <ch375.h>
+
+/* This file defines the routines that are used by ch375.h to actually IO
+ * to the underlying device. */
+
+struct ch375 {
+ uint16_t io_base;
+};
+
+static inline uint8_t ch375_read_data(struct ch375 *dev)
+{
+ return inp(dev->io_base + CH375_PORT_DATA);
+}
+
+static inline void ch375_write_data(struct ch375 *dev, uint8_t data)
+{
+ outp(dev->io_base + CH375_PORT_DATA, data);
+}
+
+static inline void ch375_send_command(struct ch375 *dev, enum ch375_command cmd)
+{
+ outp(dev->io_base + CH375_PORT_CMD, cmd);
+}
+
+/// True if interrupt line is currently active (i.e. low)
+static inline bool ch375_poll_interrupt(struct ch375 *dev)
+{
+ return !(inp(dev->io_base + CH375_PORT_CMD) & 0x80);
+}
+
+
+/** Reads one transfer block (64 bytes max). */
+#if _M_IX86 >= 200
+// Watcom does not seem able to synthesize rep ins/outs. Do it in 286+ mode.
+static uint8_t ch375_read_data_block(struct ch375 *dev, uint8_t far *buffer);
+#pragma aux ch375_read_data_block = \
+ "mov dx, [bx]" \
+ "in al, dx" \
+ "movzx cx, al" \
+ "rep ins byte ptr es:[di], dx" \
+ __parm [bx] [es di] \
+ __value [al] \
+ __modify exact [cx dx di]
+#else
+static uint8_t ch375_read_data_block(struct ch375 *dev, uint8_t far *buffer)
+{
+ uint8_t i, n = ch375_read_data(dev);
+
+ for (i = 0; i < n; i++) {
+ buffer[i] = ch375_read_data(dev);
+ }
+
+ return n;
+}
+#endif
+
+#if _M_IX86 >= 200
+static void ch375_write_data_block(struct ch375 *dev, uint8_t far * buffer, uint8_t n);
+#pragma aux ch375_write_data_block = \
+ "mov dx, [bx]" \
+ "out dx, al" \
+ "movzx cx, al" \
+ "rep outs dx, byte ptr es:[si]" \
+ __parm [bx] [es si] [al] \
+ __modify exact [cx dx si]
+#else
+static void ch375_write_data_block(struct ch375 *dev, uint8_t far * buffer, uint8_t n)
+{
+ uint8_t i;
+
+ ch375_write_data(dev, n);
+
+ for (i = 0; i < n; i++) {
+ ch375_write_data(dev, buffer[i]);
+ }
+}
+#endif
+
+#endif // CH375DOS_H
diff --git a/chud.c b/dos/chud.c
index c6d728c..c6d728c 100644
--- a/chud.c
+++ b/dos/chud.c
diff --git a/chudinit.c b/dos/chudinit.c
index 1919100..3dfb782 100644
--- a/chudinit.c
+++ b/dos/chudinit.c
@@ -20,12 +20,13 @@
#include <stdbool.h>
#include <string.h>
+#include <chuver.h>
+
#include "inc/dosapi.h"
#include "inc/dosint.h"
#include "inc/bda.h"
#include "chudisk.h"
-#include "ch375.h"
#include "dlog.h"
#include "cio.h"
#include "utils.h"
@@ -94,7 +95,7 @@ static bool parse_cmdline(const char far *cmdline)
case 'p': // IO port
num = parse_hex(&s);
if (num > 0) {
- ch375_port = num;
+ ch375_dev.io_base = num;
} else {
c_printf("invalid value '%d' for option '%c'\n", num, opt);
return false;
@@ -155,7 +156,7 @@ void driver_init(DOS_DRS __far *drs)
dlog_init();
// Defaults
- ch375_port = 0x260;
+ ch375_dev.io_base = 0x260;
num_units = 1;
read_only = false;
show_fat32 = false;
@@ -177,20 +178,20 @@ void driver_init(DOS_DRS __far *drs)
// Try to initialize CH375
dputs("CH375 reset...");
- ch375_reset();
+ ch375_reset(&ch375_dev);
bda_wait_ticks(3); // wait 100-150ms
dputs("CH375 reset wait done");
- if (ch375_check_exists(0xA5) != (uint8_t)~0xA5) {
- c_printf("CHUDISK: CH375 not detected at @%x", ch375_port);
+ if (ch375_check_exists(&ch375_dev, 0xA5) != (uint8_t)~0xA5) {
+ c_printf("CHUDISK: CH375 not detected at @%x\n", ch375_dev.io_base);
goto fail;
}
- hw_version = ch375_get_version();
+ hw_version = ch375_get_version(&ch375_dev);
// Do not really set up the chip until it is used
- ch375_set_usb_mode(CH375_USB_MODE_HOST_DISABLED);
- ch375_enter_sleep();
+ ch375_set_usb_mode(&ch375_dev, CH375_USB_MODE_HOST_DISABLED);
+ ch375_enter_sleep(&ch375_dev);
data.ready = false;
data.sectors = 0;
@@ -206,10 +207,14 @@ void driver_init(DOS_DRS __far *drs)
drs->cmd.init.out.break_addr = MK_FP(get_cs(), FP_OFF(&resident_end));
drs->status = DOS_DD_STATUS_DONE;
- c_printf("CHUDISK v.0 (HW v.%x), drives", hw_version);
+ c_printf("CHUDISK %x.%x (HW v.%x), drives",
+ CHUDISK_VERSION_MAJOR, CHUDISK_VERSION_MINOR, hw_version);
for (i = 0; i < num_units; i++) {
c_printf(" %c:", drive_index_to_letter(drs->cmd.init.in.first_drive_number + i));
}
+#if ENABLE_DLOG
+ c_printf(" (debug logging)");
+#endif
c_putc('\n');
return;
diff --git a/chudisk.c b/dos/chudisk.c
index 24de25e..c7c0968 100644
--- a/chudisk.c
+++ b/dos/chudisk.c
@@ -25,7 +25,6 @@
#include "inc/mbr.h"
#include "chudisk.h"
-#include "ch375.h"
#include "dlog.h"
#include "utils.h"
@@ -48,9 +47,10 @@ const DOS_DD_HEADER __based(__segname("_CODE")) header = {
/** Strategy routine saves DRS here for interrupt routine to use */
DOS_DRS far * saved_drs;
+/** Device struct (used by ch375.h functions) */
+struct ch375 ch375_dev;
+
// 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) */
@@ -69,16 +69,15 @@ char buffer[512];
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) {
+ while (!ch375_poll_interrupt(&ch375_dev) && 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);
+ if (ch375_poll_interrupt(&ch375_dev)) {
+ uint8_t status = ch375_get_int_status(&ch375_dev);
return status;
} else {
dprintf("CH375 int timeout\n");
@@ -111,7 +110,7 @@ static unsigned disk_read_sectors(uint8_t far *buffer, uint32_t lba, uint8_t num
dprintf("disk read buf=%Fp sector=%lu num=%u\n", buffer, lba, num);
// Initiate read operation
- ch375_disk_read(lba, num);
+ ch375_disk_read(&ch375_dev, 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) {
@@ -123,8 +122,8 @@ static unsigned disk_read_sectors(uint8_t far *buffer, uint32_t lba, uint8_t num
break;
}
- ch375_send_command(CH375_RD_USB_DATA);
- len = ch375_read_data_block(&buffer[total]);
+ ch375_send_command(&ch375_dev, CH375_RD_USB_DATA);
+ len = ch375_read_data_block(&ch375_dev, &buffer[total]);
//dprintf(" disk read data block len = %d\n", len);
@@ -136,7 +135,7 @@ static unsigned disk_read_sectors(uint8_t far *buffer, uint32_t lba, uint8_t num
total += len;
// Continue
- ch375_send_command(CH375_DISK_RD_GO);
+ ch375_send_command(&ch375_dev, CH375_DISK_RD_GO);
}
dprintf(" disk read total bytes = %u, sectors = %u, final status = 0x%hx\n",
@@ -156,11 +155,11 @@ static unsigned disk_write_sectors(uint8_t far *buffer, uint32_t lba, uint8_t nu
dprintf("disk write buf=%Fp sector=%lu num=%u\n", buffer, lba, num);
// Initiate operation
- ch375_disk_write(lba, num);
+ ch375_disk_write(&ch375_dev, 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;
+ const unsigned len = CH375_BLOCK_SIZE;
// Sanity check
if (total / SECTOR_SIZE >= num) {
@@ -168,13 +167,13 @@ static unsigned disk_write_sectors(uint8_t far *buffer, uint32_t lba, uint8_t nu
break;
}
- ch375_send_command(CH375_WR_USB_DATA7);
- ch375_write_data_block(&buffer[total], len);
+ ch375_send_command(&ch375_dev, CH375_WR_USB_DATA7);
+ ch375_write_data_block(&ch375_dev, &buffer[total], len);
total += len;
// Continue
- ch375_send_command(CH375_DISK_WR_GO);
+ ch375_send_command(&ch375_dev, CH375_DISK_WR_GO);
}
dprintf(" disk write total bytes = %u, sectors = %u, final status = 0x%hx\n",
@@ -190,14 +189,14 @@ static bool disk_connect()
int tries;
dputs("switch to usb host");
- ch375_set_usb_mode(CH375_USB_MODE_HOST_AUTO_SOF);
+ ch375_set_usb_mode(&ch375_dev, 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();
+ ch375_disk_init(&ch375_dev);
if (wait_for_ch375_result(20) != CH375_INT_USB_SUCCESS) {
dputs("could not init disk");
return false;
@@ -207,7 +206,7 @@ static bool disk_connect()
// even if each time we ask we are already polling
tries = 3;
while (tries-- > 0) {
- ch375_test_disk_ready();
+ ch375_test_disk_ready(&ch375_dev);
if (wait_for_ch375_result(20) == CH375_INT_USB_SUCCESS) {
dputs("disk now init");
return true;
@@ -236,13 +235,13 @@ static int disk_scan()
}
// Refresh disk size
- ch375_send_command(CH375_DISK_SIZE);
+ ch375_send_command(&ch375_dev, 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) {
+ ch375_send_command(&ch375_dev, CH375_RD_USB_DATA);
+ if (ch375_read_data_block(&ch375_dev, buffer) < 8) {
dprintf("disk size command return less than 8 bytes\n");
return DOS_DD_ERR_UNKNOWN_MEDIA;
}
@@ -311,7 +310,7 @@ static int disk_scan()
static int disk_init()
{
- ch375_test_disk_ready();
+ ch375_test_disk_ready(&ch375_dev);
if (wait_for_ch375_result(4) != CH375_INT_USB_SUCCESS) {
// Disk is not even connected, try to connect
if (!disk_connect()) {
@@ -396,7 +395,7 @@ static bool unit_check_media_changed(uint8_t unit)
{
if (data.ready) {
// Recheck if disk is still ready
- ch375_test_disk_ready();
+ ch375_test_disk_ready(&ch375_dev);
if (wait_for_ch375_result(4) == CH375_INT_USB_SUCCESS) {
// Disk is ready. But is partition ready ?
if (data.unit[unit].ready) {
@@ -604,8 +603,8 @@ void interrupt_routine()
int len = 0;
// B = base port (2 byte)
buf[len++] = 'B';
- _fmemcpy(&buf[len], &ch375_port, sizeof(ch375_port));
- len += sizeof(ch375_port);
+ _fmemcpy(&buf[len], &ch375_dev.io_base, sizeof(ch375_dev.io_base));
+ len += sizeof(ch375_dev.io_base);
// 3 = showing FAT32 (1 byte)
buf[len++] = '3';
buf[len++] = show_fat32;
@@ -839,7 +838,7 @@ void interrupt_routine()
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);
+ ciod->rwabs.sector, ciod->rwabs.sectors, ciod->rwabs.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;
diff --git a/chudisk.h b/dos/chudisk.h
index f3761fb..1d2fbe4 100644
--- a/chudisk.h
+++ b/dos/chudisk.h
@@ -22,14 +22,14 @@
#include <stdbool.h>
#include "inc/dosdrv.h"
+#include "ch375dos.h"
#define MAX_UNITS 4
#define SECTOR_SIZE 512
// Configuration values shared between driver init code and resident code
-/* Used by ch375.h, too. */
-extern unsigned short ch375_port;
+extern struct ch375 ch375_dev;
extern unsigned char num_units;
@@ -37,6 +37,7 @@ extern bool read_only;
extern bool show_fat32;
+/** Runtime data */
extern struct chudisk_data
{
/** Pointers to all BPBs, to return to DOS during init. */
@@ -45,7 +46,9 @@ extern struct chudisk_data
/** Whether we are ready to handle IO, i.e. disk init, partitions scanned. */
bool ready;
+ /** Current media: number of sectors. */
uint32_t sectors;
+ /** Current media: bytes per sector (usually 512). */
uint32_t bytesPerSector;
struct chudisk_unit {
diff --git a/chudisk.lnk b/dos/chudisk.lnk
index bec3794..1768998 100644
--- a/chudisk.lnk
+++ b/dos/chudisk.lnk
@@ -1,4 +1,4 @@
format dos com
option statics
-option map=chudisk.map
+option map
diff --git a/cio.c b/dos/cio.c
index 43a2a27..43a2a27 100644
--- a/cio.c
+++ b/dos/cio.c
diff --git a/cio.h b/dos/cio.h
index 24a67ae..24a67ae 100644
--- a/cio.h
+++ b/dos/cio.h
diff --git a/inc/bda.h b/dos/inc/bda.h
index b447221..b447221 100644
--- a/inc/bda.h
+++ b/dos/inc/bda.h
diff --git a/inc/bpb.h b/dos/inc/bpb.h
index 8df04a1..8df04a1 100644
--- a/inc/bpb.h
+++ b/dos/inc/bpb.h
diff --git a/inc/dosapi.h b/dos/inc/dosapi.h
index 18dbde0..18dbde0 100644
--- a/inc/dosapi.h
+++ b/dos/inc/dosapi.h
diff --git a/inc/dosdrv.h b/dos/inc/dosdrv.h
index 0c33c54..0c33c54 100644
--- a/inc/dosdrv.h
+++ b/dos/inc/dosdrv.h
diff --git a/inc/dosint.h b/dos/inc/dosint.h
index cfa855d..cfa855d 100644
--- a/inc/dosint.h
+++ b/dos/inc/dosint.h
diff --git a/inc/mbr.h b/dos/inc/mbr.h
index d45727d..d45727d 100644
--- a/inc/mbr.h
+++ b/dos/inc/mbr.h
diff --git a/dos/makefile b/dos/makefile
new file mode 100644
index 0000000..aa328c2
--- /dev/null
+++ b/dos/makefile
@@ -0,0 +1,66 @@
+#!/usr/bin/env wmake
+
+# Debug Settings
+!ifeq debug 1
+# Dump debug log to serial port, 0x3F8 UART (COM1)
+debug_cflags = -DENABLE_DLOG=1 -DDLOG_TARGET=DLOG_TARGET_SERIAL -DDLOG_TARGET_PORT=0x3F8
+!endif
+
+# All binaries to build
+bins = chudisk.sys chud.exe utest.exe
+
+# Object files for chudisk
+chudisk_objs = chudisk.obj chudinit.obj cio.obj
+
+# Compiler arguments for DOS
+dos_cflags = -bt=dos -i=../inc -3 -ms -osi -ox -w3 -wcd=202 $(debug_cflags)
+# -ms to use small memory model (this assumes ss == ds)
+# -osi to optimize for size, put intrinsics inline (to avoid runtime calls)
+# -ox extra optimization (impact is not very big)
+# -w3 enables warnings
+# -wcd=202 disables the unreferenced function warning (e.g., for inline functions in headers)
+
+# Compiler arguments for the DOS drivers
+dosdrv_cflags = $(dos_cflags) -zu -s -g=DGROUP
+# -zu since ss != ds in the driver
+# -s to disable stack checks, since it inserts calls to the runtime
+# -g=DGROUP put everything into the same segment
+
+# Full compiler command lines
+compile_dos = *wcc -fo=$^@ $(dos_cflags) $[@
+compile_dosdrv = *wcc -fo=$^@ $(dosdrv_cflags) $[@
+
+# Object files
+
+.BEFORE:
+ # We need DOS and Windows headers, not host platform's
+ set include=$(%watcom)/h/win;$(%watcom)/h
+
+all: $(bins) .SYMBOLIC
+
+chudisk.sys: chudisk.lnk $(chudisk_objs) .AUTODEPEND
+ *wlink @$[@ name $@ file { $(chudisk_objs) }
+
+chudisk.obj: chudisk.c .AUTODEPEND
+ $(compile_dosdrv)
+
+chudinit.obj: chudinit.c .AUTODEPEND
+ $(compile_dosdrv)
+
+cio.obj: cio.c .AUTODEPEND
+ $(compile_dosdrv)
+
+chud.exe: chud.obj .AUTODEPEND
+ *wlink name $@ system dos file { chud.obj }
+
+chud.obj: chud.c .AUTODEPEND
+ $(compile_dos)
+
+utest.exe: utest.obj .AUTODEPEND
+ *wlink system dos name $@ file { utest.obj }
+
+utest.obj: utest.c .AUTODEPEND
+ $(compile_dos)
+
+clean: .SYMBOLIC
+ rm -f $(bins) *.obj
diff --git a/test.c b/dos/utest.c
index 3cb2ffd..7aa8750 100644
--- a/test.c
+++ b/dos/utest.c
@@ -3,10 +3,11 @@
#include <time.h>
#include <dos.h>
-#include "usb.h"
-#include "ch375.h"
+#include "ch375dos.h"
-unsigned short ch375_port = 0x260;
+struct ch375 ch375_dev = {
+ .io_base = 0x260
+};
#define SWAPNC_32(w) (\
(((w) & 0x000000FFUL) << 24) |\
@@ -60,7 +61,6 @@ static bool wait_with_timeout(Centisecs timeout, PollFunction pf) {
Centisecs deadline = start + timeout;
Centisecs last_print = 0;
while (!pf() && centisecs_remaining(start, deadline)) {
- ch375_iodelay();
if (last_print != get_centisecs()) {
printf("Wait with timeout deadline=%u now=%u\n", deadline, get_centisecs());
last_print = get_centisecs();
@@ -76,40 +76,43 @@ static bool wait_with_timeout(Centisecs timeout, PollFunction pf) {
//return pf();
}
-static bool poll_test_connect(void) {
- return ch375_read_data() != 0;
+static bool poll_interrupt(void) {
+ return ch375_poll_interrupt(&ch375_dev);
}
+static bool poll_test_connect(void) {
+ return ch375_read_data(&ch375_dev) != 0;
+}
static bool wait_interrupt_with_timeout(unsigned int timeout) {
- return wait_with_timeout(timeout, ch375_poll_interrupt);
+ return wait_with_timeout(timeout, poll_interrupt);
}
static void dump_int_status()
{
- char i = ch375_poll_interrupt() ? 'I' : ' ';
- printf("Int Status: [%c] 0x%hx\n", i, ch375_get_int_status());
+ char i = ch375_poll_interrupt(&ch375_dev) ? 'I' : ' ';
+ printf("Int Status: [%c] 0x%hx\n", i, ch375_get_int_status(&ch375_dev));
// Above also clears pending ints...
}
void chip_reset()
{
printf("Chip reset\n");
- ch375_reset();
+ ch375_reset(&ch375_dev);
delay_centisecs(5);
}
void chip_sleep()
{
printf("Chip sleep\n");
- ch375_enter_sleep();
+ ch375_enter_sleep(&ch375_dev);
}
void usb_host_off()
{
printf("Set usb mode disabled...\n");
- ch375_set_usb_mode(CH375_USB_MODE_HOST_DISABLED);
- if (!wait_with_timeout(1, poll_test_connect) || ch375_read_data() != CH375_RET_SUCESS) {
+ ch375_set_usb_mode(&ch375_dev, CH375_USB_MODE_HOST_DISABLED);
+ if (!wait_with_timeout(1, poll_test_connect) || ch375_read_data(&ch375_dev) != CH375_RET_SUCESS) {
printf("Set usb mode failed\n");
}
}
@@ -117,8 +120,8 @@ void usb_host_off()
void usb_host_reset(void)
{
printf("Set usb mode reset...\n");
- ch375_set_usb_mode(CH375_USB_MODE_HOST_RESET);
- if (!wait_with_timeout(1, poll_test_connect) || ch375_read_data() != CH375_RET_SUCESS) {
+ ch375_set_usb_mode(&ch375_dev, CH375_USB_MODE_HOST_RESET);
+ if (!wait_with_timeout(1, poll_test_connect) || ch375_read_data(&ch375_dev) != CH375_RET_SUCESS) {
printf("Set usb mode reset failed\n");
}
}
@@ -126,8 +129,8 @@ void usb_host_reset(void)
bool usb_host_on(void)
{
printf("Set usb mode...\n");
- ch375_set_usb_mode(CH375_USB_MODE_HOST_AUTO_SOF);
- if (!wait_with_timeout(1, poll_test_connect) || ch375_read_data() != CH375_RET_SUCESS) {
+ ch375_set_usb_mode(&ch375_dev, CH375_USB_MODE_HOST_AUTO_SOF);
+ if (!wait_with_timeout(1, poll_test_connect) || ch375_read_data(&ch375_dev) != CH375_RET_SUCESS) {
printf("Set usb mode failed\n");
return false;
}
@@ -142,11 +145,11 @@ bool usb_host_connect(void)
uint8_t status;
printf("Test connect...\n");
- ch375_test_connect();
+ ch375_test_connect(&ch375_dev);
if (!wait_with_timeout(1, poll_test_connect)) {
printf("Test connect timedout\n");
}
- status = ch375_read_data();
+ status = ch375_read_data(&ch375_dev);
printf("Test connect status = 0x%hx\n", status);
switch (status) {
case CH375_INT_USB_CONNECT:
@@ -165,21 +168,20 @@ bool usb_host_connect(void)
static int usb_host_wait_int_read_data(Centisecs timeout, uint8_t buffer[])
{
uint8_t status;
- if (!wait_with_timeout(timeout, ch375_poll_interrupt)) {
+ if (!wait_with_timeout(timeout, poll_interrupt)) {
return -1;
}
// Get and acknowledge interrupt
- status = ch375_get_int_status();
+ status = ch375_get_int_status(&ch375_dev);
printf("Wait int read data status=0x%hx\n", status);
if (status != CH375_INT_USB_SUCCESS) {
return -status;
}
-
// USB host mode doesn't need to do unlock, so we can use DATA0 here?
- ch375_send_command(CH375_RD_USB_DATA0);
- return ch375_read_data_block(buffer);
+ ch375_send_command(&ch375_dev, CH375_RD_USB_DATA0);
+ return ch375_read_data_block(&ch375_dev, buffer);
}
static bool init_disk()
@@ -187,11 +189,11 @@ static bool init_disk()
uint8_t status;
printf("Disk init...\n");
- ch375_disk_init();
+ ch375_disk_init(&ch375_dev);
if (!wait_interrupt_with_timeout(100)) {
printf("Disk init timeout\n");
}
- status = ch375_get_int_status();
+ status = ch375_get_int_status(&ch375_dev);
printf("Disk init status = 0x%hx\n", status);
return status == CH375_INT_USB_SUCCESS;
@@ -200,11 +202,11 @@ static bool init_disk()
static bool disk_ready()
{
uint8_t status;
- ch375_test_disk_ready();
+ ch375_test_disk_ready(&ch375_dev);
if (!wait_interrupt_with_timeout(100)) {
printf("Disk ready timeout\n");
}
- status = ch375_get_int_status();
+ status = ch375_get_int_status(&ch375_dev);
printf("Disk ready status = 0x%hx\n", status);
return status == CH375_INT_USB_SUCCESS;
}
@@ -214,19 +216,19 @@ static void disk_size()
uint8_t status;
printf("Disk size...\n");
- ch375_send_command(CH375_DISK_SIZE);
+ ch375_send_command(&ch375_dev, CH375_DISK_SIZE);
if (!wait_interrupt_with_timeout(100)) {
printf("Disk size timeout\n");
}
- status = ch375_get_int_status();
+ status = ch375_get_int_status(&ch375_dev);
printf("Disk size status = 0x%hx\n", status);
if (status == CH375_INT_USB_SUCCESS) {
int len;
- ch375_send_command(CH375_RD_USB_DATA);
+ ch375_send_command(&ch375_dev, CH375_RD_USB_DATA);
- len = ch375_read_data_block(buffer);
+ len = ch375_read_data_block(&ch375_dev, buffer);
printf("Read %d bytes\n", len);
if (len >= 8) {
@@ -287,25 +289,25 @@ int disk_read(uint32_t lba)
uint8_t status;
unsigned total = 0;
unsigned i;
- ch375_disk_read(lba, 1);
+ ch375_disk_read(&ch375_dev, lba, 1);
if (!wait_interrupt_with_timeout(100)) {
printf("Disk read timeout\n");
}
- while ((status = ch375_get_int_status() == CH375_INT_USB_DISK_READ)) {
+ while ((status = ch375_get_int_status(&ch375_dev) == CH375_INT_USB_DISK_READ)) {
int len;
- ch375_send_command(CH375_RD_USB_DATA);
+ ch375_send_command(&ch375_dev, CH375_RD_USB_DATA);
if (total >= 512) {
printf("Read buffer overflow!");
break;
}
- len = ch375_read_data_block(&buffer[total]);
+ len = ch375_read_data_block(&ch375_dev, &buffer[total]);
printf("Disk read data block len = %d\n", len);
if (len > 0) {
total += len;
- ch375_send_command(CH375_DISK_RD_GO);
+ ch375_send_command(&ch375_dev, CH375_DISK_RD_GO);
if (!wait_interrupt_with_timeout(100)) {
printf("Disk read more timeout\n");
}
@@ -343,19 +345,19 @@ int autou()
#endif
#if 1
- // TODO: Try to get descriptor in high speed, then retry in low speed?
- ch375_set_usb_speed(CH375_USB_SPEED_LOW);
+ // TODO: Try to get descriptor in high speed, then retry in low speed?
+ ch375_set_usb_speed(&ch375_dev, CH375_USB_SPEED_LOW);
#endif
#if 1
printf("Auto setup...\n");
- ch375_auto_setup();
- ch375_send_command(CH375_AUTO_SETUP);
+ ch375_auto_setup(&ch375_dev);
+ ch375_send_command(&ch375_dev, CH375_AUTO_SETUP);
if (!wait_interrupt_with_timeout(100)) {
printf("Auto setup timeout\n");
}
- status = ch375_get_int_status();
+ status = ch375_get_int_status(&ch375_dev);
printf("Auto setup status = 0x%hx\n", status);
delay(100);
@@ -374,11 +376,10 @@ int autou()
int host()
{
- uint8_t status;
usb_device_descriptor *dev_descr;
int n;
- //ch375_set_retry(CH375_RETRY_NAK_INFINITE | CH375_RETRY_TIMEOUT_MAX);
+ //ch375_set_retry(&ch375_dev, CH375_RETRY_NAK_INFINITE | CH375_RETRY_TIMEOUT_MAX);
#if 0
usb_host_reset();
@@ -387,15 +388,14 @@ int host()
#if 1
if (!usb_host_on()) {
-
return EXIT_FAILURE;
}
//delay(100);
#endif
#if 1
- // TODO: Try to get descriptor in high speed, then retry in low speed?
- ch375_set_usb_speed(CH375_USB_SPEED_LOW);
+ // TODO: Try to get descriptor in high speed, then retry in low speed?
+ ch375_set_usb_speed(&ch375_dev, CH375_USB_SPEED_LOW);
#endif
#if 0
@@ -407,7 +407,7 @@ int host()
dump_int_status();
printf("Reading descriptor %d...\n", USB_DESCRIPTOR_TYPE_DEVICE);
- ch375_get_descriptor(USB_DESCRIPTOR_TYPE_DEVICE);
+ ch375_get_descriptor(&ch375_dev, USB_DESCRIPTOR_TYPE_DEVICE);
n = usb_host_wait_int_read_data(100, buffer);
if (n >= 0) {
printf("Got descriptor 1 size = %d\n", n);
@@ -423,7 +423,7 @@ int host()
printf("Got USB device VID:PID %04x:%04x\n", dev_descr->idVendor, dev_descr->idProduct);
printf("Reading descriptor %d...\n", USB_DESCRIPTOR_TYPE_CONFIG);
- ch375_get_descriptor(USB_DESCRIPTOR_TYPE_CONFIG);
+ ch375_get_descriptor(&ch375_dev, USB_DESCRIPTOR_TYPE_CONFIG);
n = usb_host_wait_int_read_data(100, buffer);
if (n >= 0) {
printf("Got descriptor 2 size = %d\n", n);
@@ -439,13 +439,19 @@ int host()
int main(int argc, const char *argv[])
{
- char mode = 'd';
+ char mode = '?';
+ char *port = getenv("CH375_PORT");
+
+ if (port) {
+ unsigned long lport = strtoul(port, NULL, 16);
+ ch375_dev.io_base = lport;
+ }
if (argc > 1) {
mode = argv[1][0];
}
- printf("Test mode %c\n", mode);
+ printf("Test mode %c using IO port 0x%x\n", mode, ch375_dev.io_base);
dump_int_status();
@@ -460,30 +466,35 @@ int main(int argc, const char *argv[])
return host();
case 'h':
usb_host_on();
- return EXIT_SUCCESS;
+ break;
case 'c':
usb_host_connect();
- return EXIT_SUCCESS;
+ break;
case 'o':
usb_host_off();
- return EXIT_SUCCESS;
+ break;
case 'b':
usb_host_reset();
- return EXIT_SUCCESS;
+ break;
case 's':
chip_sleep();
- return EXIT_SUCCESS;
+ break;
case 'r':
chip_reset();
- return EXIT_SUCCESS;
+ break;
case 'i':
dump_int_status();
- return EXIT_SUCCESS;
+ break;
case 'v':
- printf("IC Version: 0x%hx\n", ch375_get_version());
- return EXIT_SUCCESS;
+ printf("IC Version: 0x%hx\n", ch375_get_version(&ch375_dev));
+ break;
+ case '?':
+ printf("test modes: d e a m h c o b s r i v\n");
+ break;
default:
printf("Unknown mode %c\n", mode);
return EXIT_FAILURE;
}
+
+ return EXIT_SUCCESS;
}
diff --git a/utils.h b/dos/utils.h
index 138a774..138a774 100644
--- a/utils.h
+++ b/dos/utils.h
diff --git a/ch375.h b/inc/ch375.h
index a1ff1b0..5ce90a2 100644
--- a/ch375.h
+++ b/inc/ch375.h
@@ -20,14 +20,11 @@
#ifndef CH375_H
#define CH375_H
-#include <stdint.h>
-#include <stdbool.h>
-#include <conio.h>
-#include <i86.h>
-
#include "usb.h"
-extern unsigned short ch375_port;
+/** Standard defines */
+
+struct ch375;
enum {
CH375_PORT_DATA = 0,
@@ -165,35 +162,6 @@ enum ch375_int {
CH375_INT_USB_DISK_ERR = 0x1F,
};
-static inline void ch375_iodelay(void)
-{
- // Doesn't look to be required on pocket386
- //outp(0x80, 0);
-}
-
-static inline uint8_t ch375_read_data(void)
-{
- return inp(ch375_port + CH375_PORT_DATA);
-}
-
-static inline void ch375_write_data(uint8_t data)
-{
- outp(ch375_port + CH375_PORT_DATA, data);
- ch375_iodelay();
-}
-
-static inline void ch375_send_command(enum ch375_command cmd)
-{
- outp(ch375_port + CH375_PORT_CMD, cmd);
- ch375_iodelay();
-}
-
-/// True if interrupt line is currently active (i.e. low)
-static inline bool ch375_poll_interrupt(void)
-{
- return !(inp(ch375_port + CH375_PORT_CMD) & 0x80);
-}
-
enum CH375_REG {
/** (Host mode) Get data rate of current USB device.
* Bit 4: if 1= Low Speed. if 0 = Full Speed.*/
@@ -229,96 +197,61 @@ enum CH375_REG {
};
-static uint8_t ch375_read_reg(enum CH375_REG reg)
-{
- ch375_send_command(CH375_READ_REG);
- ch375_write_data(reg);
- return ch375_read_data();
-}
+#define CH375_BLOCK_SIZE 64
-static void ch375_write_reg(enum CH375_REG reg, uint8_t value)
-{
- ch375_send_command(CH375_WRITE_REG);
- ch375_write_data(reg);
- ch375_write_data(value);
-}
+// HELPER ROUTINES
-/** Reads one transfer block (64 bytes max). */
-#if _M_IX86 >= 200
-// Watcom does not seem able to synthesize rep ins/outs. Do it in 286+ mode.
-static uint8_t ch375_read_data_block(uint8_t far *buffer);
-#pragma aux ch375_read_data_block = \
- "mov dx, [ch375_port]" \
- "in al, dx" \
- "movzx cx, al" \
- "rep ins byte ptr es:[di], dx" \
- __parm [es di] \
- __value [al] \
- __modify exact [cx dx di]
-#else
-static uint8_t ch375_read_data_block(uint8_t far *buffer)
-{
- uint8_t i, n = ch375_read_data();
+/* You have to define these routines for the helper functions to work. */
- for (i = 0; i < n; i++) {
- buffer[i] = ch375_read_data();
- }
+static inline uint8_t ch375_read_data(struct ch375 *dev);
+static inline void ch375_write_data(struct ch375 *dev, uint8_t data);
- return n;
-}
-#endif
-
-#if _M_IX86 >= 200
-static void ch375_write_data_block(uint8_t far * buffer, uint8_t n);
-#pragma aux ch375_write_data_block = \
- "mov dx, [ch375_port]" \
- "out dx, al" \
- "movzx cx, al" \
- "rep outs dx, byte ptr es:[si]" \
- __parm [es si] [al] \
- __modify exact [cx dx si]
-#else
-static void ch375_write_data_block(uint8_t far * buffer, uint8_t n)
-{
- uint8_t i;
+static inline void ch375_send_command(struct ch375 *dev, enum ch375_command cmd);
+static inline bool ch375_poll_interrupt(struct ch375 *dev);
- ch375_write_data(n);
+// GLOBAL COMMANDS
- for (i = 0; i < n; i++) {
- ch375_write_data(buffer[i]);
- }
+static inline uint8_t ch375_get_version(struct ch375 *dev)
+{
+ ch375_send_command(dev, CH375_GET_IC_VERSION);
+ return ch375_read_data(dev) & 0x3F;
}
-#endif
-// GLOBAL COMMANDS
+static inline void ch375_enter_sleep(struct ch375 *dev)
+{
+ ch375_send_command(dev, CH375_ENTER_SLEEP);
+}
-static inline uint8_t ch375_get_version(void)
+static inline void ch375_reset(struct ch375 *dev)
{
- ch375_send_command(CH375_GET_IC_VERSION);
- return ch375_read_data() & 0x3F;
+ ch375_send_command(dev, CH375_RESET);
}
-static inline void ch375_enter_sleep(void)
+static inline uint8_t ch375_check_exists(struct ch375 *dev, uint8_t val)
{
- ch375_send_command(CH375_ENTER_SLEEP);
+ ch375_send_command(dev, CH375_CHECK_EXIST);
+ ch375_write_data(dev, val);
+ return ch375_read_data(dev);
}
-static inline void ch375_reset(void)
+static inline uint8_t ch375_get_int_status(struct ch375 *dev)
{
- ch375_send_command(CH375_RESET);
+ ch375_send_command(dev, CH375_GET_INT_STATUS);
+ return ch375_read_data(dev);
}
-static inline uint8_t ch375_check_exists(uint8_t val)
+static uint8_t ch375_read_reg(struct ch375 *dev, enum CH375_REG reg)
{
- ch375_send_command(CH375_CHECK_EXIST);
- ch375_write_data(val);
- return ch375_read_data();
+ ch375_send_command(dev, CH375_READ_REG);
+ ch375_write_data(dev, reg);
+ return ch375_read_data(dev);
}
-static inline uint8_t ch375_get_int_status(void)
+static void ch375_write_reg(struct ch375 *dev, enum CH375_REG reg, uint8_t value)
{
- ch375_send_command(CH375_GET_INT_STATUS);
- return ch375_read_data();
+ ch375_send_command(dev, CH375_WRITE_REG);
+ ch375_write_data(dev, reg);
+ ch375_write_data(dev, value);
}
enum ch375_usb_mode {
@@ -331,9 +264,9 @@ enum ch375_usb_mode {
CH375_USB_MODE_HOST_RESET = 7
};
-static void ch375_set_usb_mode(enum ch375_usb_mode mode) {
- ch375_send_command(CH375_SET_USB_MODE);
- ch375_write_data(mode);
+static void ch375_set_usb_mode(struct ch375 *dev, enum ch375_usb_mode mode) {
+ ch375_send_command(dev, CH375_SET_USB_MODE);
+ ch375_write_data(dev, mode);
}
enum ch375_usb_speed {
@@ -342,24 +275,24 @@ enum ch375_usb_speed {
};
// Seems to be for CH375B only. See below for CH375A alternative.
-static void ch375_set_usb_speed(enum ch375_usb_speed speed) {
- ch375_send_command(CH375_SET_USB_SPEED);
- ch375_write_data(speed);
+static void ch375_set_usb_speed(struct ch375 *dev, enum ch375_usb_speed speed) {
+ ch375_send_command(dev, CH375_SET_USB_SPEED);
+ ch375_write_data(dev, speed);
}
-static void ch375a_set_low_speed(void) {
+static void ch375a_set_low_speed(struct ch375 *dev) {
// Undocumented.
- ch375_write_reg(0x17, 0xd8);
+ ch375_write_reg(dev, 0x17, 0xd8);
}
// HOST COMMANDS
-static void ch375_test_connect(void) {
- ch375_send_command(CH375_TEST_CONNECT);
+static void ch375_test_connect(struct ch375 *dev) {
+ ch375_send_command(dev, CH375_TEST_CONNECT);
}
-static void ch375_auto_setup(void) {
- ch375_send_command(CH375_AUTO_SETUP);
+static void ch375_auto_setup(struct ch375 *dev) {
+ ch375_send_command(dev, CH375_AUTO_SETUP);
}
enum ch375_retry_mode {
@@ -370,13 +303,13 @@ enum ch375_retry_mode {
CH375_RETRY_TIMEOUT_MAX = 0x1F
};
-static void ch375_set_retry(enum ch375_retry_mode mode) {
- ch375_write_reg(CH375_REG_RETRY, mode);
+static void ch375_set_retry(struct ch375 *dev, enum ch375_retry_mode mode) {
+ ch375_write_reg(dev, CH375_REG_RETRY, mode);
}
-static void ch375_get_descriptor(usb_descriptor_type type) {
- ch375_send_command(CH375_GET_DESCR);
- ch375_write_data(type);
+static void ch375_get_descriptor(struct ch375 *dev, usb_descriptor_type type) {
+ ch375_send_command(dev, CH375_GET_DESCR);
+ ch375_write_data(dev, type);
}
// DISK COMMANDS
@@ -385,39 +318,44 @@ enum ch375_reg_disk_status {
CH375_REG_DISK_STATUS_READY = 0x20
};
-static inline bool ch375_is_disk_ready(void)
+static inline bool ch375_is_disk_ready(struct ch375 *dev)
+{
+ return ch375_read_reg(dev, CH375_REG_DISK_STATUS) & CH375_REG_DISK_STATUS_READY;
+}
+
+static void ch375_disk_init(struct ch375 *dev)
{
- return ch375_read_reg(CH375_REG_DISK_STATUS) & CH375_REG_DISK_STATUS_READY;
+ ch375_send_command(dev, CH375_DISK_INIT);
}
-static void ch375_disk_init()
+static void ch375_disk_read(struct ch375 *dev, uint32_t lba, uint8_t nsectors)
{
- ch375_send_command(CH375_DISK_INIT);
+ ch375_send_command(dev, CH375_DISK_READ);
+ ch375_write_data(dev, (lba >> 0) & 0xFF);
+ ch375_write_data(dev, (lba >> 8) & 0xFF);
+ ch375_write_data(dev, (lba >> 16) & 0xFF);
+ ch375_write_data(dev, (lba >> 24) & 0xFF);
+ ch375_write_data(dev, nsectors);
}
-static void ch375_disk_read(uint32_t lba, uint8_t nsectors)
+static void ch375_disk_write(struct ch375 *dev, uint32_t lba, uint8_t nsectors)
{
- ch375_send_command(CH375_DISK_READ);
- ch375_write_data((lba >> 0) & 0xFF);
- ch375_write_data((lba >> 8) & 0xFF);
- ch375_write_data((lba >> 16) & 0xFF);
- ch375_write_data((lba >> 24) & 0xFF);
- ch375_write_data(nsectors);
+ ch375_send_command(dev, CH375_DISK_WRITE);
+ ch375_write_data(dev, (lba >> 0) & 0xFF);
+ ch375_write_data(dev, (lba >> 8) & 0xFF);
+ ch375_write_data(dev, (lba >> 16) & 0xFF);
+ ch375_write_data(dev, (lba >> 24) & 0xFF);
+ ch375_write_data(dev, nsectors);
}
-static void ch375_disk_write(uint32_t lba, uint8_t nsectors)
+static void ch375_test_disk_ready(struct ch375 *dev)
{
- ch375_send_command(CH375_DISK_WRITE);
- ch375_write_data((lba >> 0) & 0xFF);
- ch375_write_data((lba >> 8) & 0xFF);
- ch375_write_data((lba >> 16) & 0xFF);
- ch375_write_data((lba >> 24) & 0xFF);
- ch375_write_data(nsectors);
+ ch375_send_command(dev, CH375_DISK_READY);
}
-static void ch375_test_disk_ready(void)
+static void ch375_disk_r_sense(struct ch375 *dev)
{
- ch375_send_command(CH375_DISK_READY);
+ ch375_send_command(dev, CH375_DISK_R_SENSE);
}
#endif
diff --git a/inc/chuver.h b/inc/chuver.h
new file mode 100644
index 0000000..f816114
--- /dev/null
+++ b/inc/chuver.h
@@ -0,0 +1,7 @@
+#ifndef CHUVER_H
+#define CHUVER_H
+
+#define CHUDISK_VERSION_MAJOR 1
+#define CHUDISK_VERSION_MINOR 0x0
+
+#endif /* CHUVER_H */
diff --git a/dlog.h b/inc/dlog.h
index 00cd5b5..ab6f4e9 100644
--- a/dlog.h
+++ b/inc/dlog.h
@@ -28,18 +28,29 @@
// Customizable defines
/** If 0, these routines become nops */
+#ifndef ENABLE_DLOG
#define ENABLE_DLOG 0
-/** 1 means target serial port, 0 means target IO port. */
-#define DLOG_TARGET DLOG_TARGET_SERIAL
+#endif
+/** Target of logging messages. See DLOG_TARGET_* below. */
+#ifndef DLOG_TARGET
+#define DLOG_TARGET DLOG_TARGET_UNDEF
+#endif
/** When using DLOG_TARGET_IOPORT, which IO port to target.
* VirtualBox uses 0x504, Bochs, DOSBox and descendants use 0xE9.
* When using DLOG_TARGET_SERIAL, the desired UART IO base port. (e.g. COM1 = 0x3F8). */
-#define DLOG_TARGET_PORT 0x3F8
+#ifndef DLOG_TARGET_PORT
+#define DLOG_TARGET_PORT 0x504
+#endif
// End of customizable defines
+/** You provide your own dlog_init and dputc routines. */
+#define DLOG_TARGET_UNDEF 0
+/** Output to the screen using BIOS TTY interface. */
#define DLOG_TARGET_BIOSTTY 1
+/** Output to a serial port. UART io base defined in DLOG_TARGET_PORT. */
#define DLOG_TARGET_SERIAL 2
+/** Output to a raw IO port defined in DLOG_TARGET_PORT.*/
#define DLOG_TARGET_IOPORT 3
#if ENABLE_DLOG
@@ -113,7 +124,11 @@ static inline void dputc(char c)
static void d_utoa(unsigned num, unsigned base)
{
+#if UINT_MAX > 99999
+ char buf[12];
+#else
char buf[6];
+#endif
int i = 0;
do {
@@ -142,9 +157,14 @@ static void d_ultoa(unsigned long num, unsigned base)
do {
unsigned digit = 0;
+ // Do unsigned long division with remained on whichever platform we are
static inline void uldiv(void);
-#pragma aux uldiv = \
- "push eax" \
+#if M_I86
+ // 16-bit code
+ #if _M_IX86 >= 300
+ // 16-bit compiler, but for running on 386+: preserve 32-bit regs and use them.
+ #pragma aux uldiv = \
+ "push eax" /* save 32-bit regs, compiler does not know about them*/ \
"push ecx" \
"push edx" \
"mov eax, [num]" \
@@ -160,6 +180,38 @@ static void d_ultoa(unsigned long num, unsigned base)
"pop ecx" \
"pop eax" \
__modify __exact [];
+ #else
+ // 16-bit compiler, 16-bit code: 16-bit division
+ #pragma aux uldiv = \
+ "mov cx, word ptr [num]" \
+ "mov ax, word ptr [num+2]" \
+ "mov bx, [base]" \
+ "xor dx, dx" \
+ \
+ "div bx" /* ax = HI(num)/base -> HI quotient, dx = HI(num)%base -> HI remainder */ \
+ "xchg ax,cx" /* cx = HI(num)/base, ax = LO(num) */ \
+ "div bx" /* ax = (HI(num)%base)/base -> true quotient , dx = (HI(num)%base)%base -> true remainder */ \
+ \
+ "mov word ptr [num], ax" \
+ "mov word ptr [num+2], cx" \
+ "mov [digit], dx" \
+ __modify __exact [];
+ #endif
+#elif M_I386
+ // 32-bit compiler, 32-bit code
+ #pragma aux uldiv = \
+ "mov eax, [num]" \
+ "mov ecx, [base]" \
+ "xor edx, edx" \
+ \
+ "div ecx" \
+ \
+ "mov [num], eax" \
+ "mov [digit], edx" \
+ __modify __exact [eax ecx edx];
+#else
+ #error unknown platform
+#endif
uldiv();
if (digit < 10) {
@@ -267,42 +319,70 @@ static void dprintf(const char *fmt, ...)
switch ((c = *fmt)) {
case 'd':
switch (size) {
+#if INT_MAX != SHRT_MAX
+ case sizeof(short):
+ d_itoa(va_arg(va, short), 10);
+ break;
+#endif
case sizeof(int):
d_itoa(va_arg(va, int), 10);
break;
+#if INT_MAX != LONG_MAX
case sizeof(long):
d_ltoa(va_arg(va, long), 10);
break;
+#endif
}
break;
case 'u':
switch (size) {
+#if INT_MAX != SHRT_MAX
+ case sizeof(short):
+ d_utoa(va_arg(va, unsigned short), 10);
+ break;
+#endif
case sizeof(int):
- d_utoa(va_arg(va, int), 10);
+ d_utoa(va_arg(va, unsigned int), 10);
break;
+#if INT_MAX != LONG_MAX
case sizeof(long):
- d_ultoa(va_arg(va, long), 10);
+ d_ultoa(va_arg(va, unsigned long), 10);
break;
+#endif
}
break;
case 'x':
switch (size) {
+#if INT_MAX != SHRT_MAX
+ case sizeof(short):
+ d_utoa(va_arg(va, unsigned short), 16);
+ break;
+#endif
case sizeof(int):
- d_utoa(va_arg(va, int), 16);
+ d_utoa(va_arg(va, unsigned int), 16);
break;
+#if INT_MAX != LONG_MAX
case sizeof(long):
- d_ultoa(va_arg(va, long), 16);
+ d_ultoa(va_arg(va, unsigned long), 16);
break;
+#endif
}
break;
case 'b':
switch (size) {
+#if INT_MAX != SHRT_MAX
+ case sizeof(short):
+ d_utoa(va_arg(va, unsigned short), 2);
+ break;
+#endif
case sizeof(int):
- d_utoa(va_arg(va, int), 2);
+ d_utoa(va_arg(va, unsigned int), 2);
break;
+#if INT_MAX != LONG_MAX
case sizeof(long):
d_ultoa(va_arg(va, long), 2);
break;
+#endif
}
break;
case 'p':
@@ -312,7 +392,7 @@ static void dprintf(const char *fmt, ...)
dputc(':');
d_utoa(FP_OFF(p), 16);
} else {
- d_utoa(va_arg(va, unsigned), 16);
+ d_utoa((unsigned) va_arg(va, void*), 16);
}
break;
case 's':
diff --git a/inc/sassert.h b/inc/sassert.h
index 71a0c0e..465c7fe 100644
--- a/inc/sassert.h
+++ b/inc/sassert.h
@@ -1,8 +1,6 @@
#ifndef SASSERT_H
#define SASSERT_H
-#include <stddef.h>
-
#define STATIC_ASSERT(expr) typedef int STATIC_ASSERT_FAILED[(expr) ? 1 : -1]
#endif // SASSERT_H
diff --git a/inc/scsi.h b/inc/scsi.h
new file mode 100644
index 0000000..a9d8b05
--- /dev/null
+++ b/inc/scsi.h
@@ -0,0 +1,61 @@
+#ifndef SCSICAM_H
+#define SCSICAM_H
+
+/** (from SCSICAM) converts a capacitity in sectors to CHS
+ * keeps cylinders <= 1024 and sectors <= 62,
+ * but may give very large head numbers, though. */
+static int setsize(unsigned long capacity, unsigned int *cyls, unsigned int *hds, unsigned int *secs)
+{
+ unsigned int rv = 0;
+ unsigned long heads, sectors, cylinders, temp;
+
+ cylinders = 1024L; /* Set number of cylinders to max */
+ sectors = 62L; /* Maximize sectors per track */
+
+ temp = cylinders * sectors; /* Compute divisor for heads */
+ heads = capacity / temp; /* Compute value for number of heads */
+ if (capacity % temp) { /* If no remainder, done! */
+ heads++; /* Else, increment number of heads */
+ temp = cylinders * heads; /* Compute divisor for sectors */
+ sectors = capacity / temp; /* Compute value for sectors per
+ track */
+ if (capacity % temp) { /* If no remainder, done! */
+ sectors++; /* Else, increment number of sectors */
+ temp = heads * sectors; /* Compute divisor for cylinders */
+ cylinders = capacity / temp; /* Compute number of cylinders */
+ }
+ }
+ if (cylinders == 0)
+ rv = (unsigned) -1; /* Give error if 0 cylinders */
+
+ *cyls = (unsigned int) cylinders; /* Stuff return values */
+ *secs = (unsigned int) sectors;
+ *hds = (unsigned int) heads;
+ return (rv);
+}
+
+static void guess_geometry(uint32_t num_sectors, unsigned int *cyls, unsigned int *hds, unsigned int *secs)
+{
+ unsigned int cylinders, heads, sectors;
+ // First try to use SCSI CAM approach
+ if (setsize(num_sectors, &cylinders, &heads, &sectors) != 0
+ || cylinders > 1024 || heads > 255 || sectors > 63) {
+ // Didn't work. So now we just use max heads and sectors but a crazy number of cyls
+ if (num_sectors <= 64 * 32 * 65534) {
+ // This follows the same behavior as Linux,
+ // which uses smaller values for heads/sects as long as cyls fits < 65535
+ heads = 64;
+ sectors = 32;
+ } else {
+ heads = 255;
+ sectors = 63;
+ }
+ cylinders = num_sectors / (heads * sectors);
+ }
+
+ *cyls = cylinders;
+ *hds = heads;
+ *secs = sectors;
+}
+
+#endif // SCSICAM_H
diff --git a/usb.h b/inc/usb.h
index d8e156e..3ea18ad 100644
--- a/usb.h
+++ b/inc/usb.h
@@ -20,14 +20,12 @@
#ifndef USB_H
#define USB_H
-#include <stdint.h>
-
typedef enum usb_descriptor_type {
USB_DESCRIPTOR_TYPE_DEVICE = 1,
USB_DESCRIPTOR_TYPE_CONFIG = 2
} usb_descriptor_type;
-typedef _Packed struct usb_device_descriptor {
+typedef struct usb_device_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bcdUSB;
@@ -42,6 +40,6 @@ typedef _Packed struct usb_device_descriptor {
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
-} usb_device_descriptor;
+} _Packed usb_device_descriptor;
#endif // USB_H
diff --git a/linux/ch375lnx.h b/linux/ch375lnx.h
new file mode 100644
index 0000000..e9309a2
--- /dev/null
+++ b/linux/ch375lnx.h
@@ -0,0 +1,76 @@
+/*
+ * CHU - IO routines to CH375 from a Linux kernel module
+ * Copyright (C) 2026 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/>.
+ */
+
+#ifndef CH375LNX_H
+#define CH375LNX_H
+
+#include <linux/types.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+#define _Packed __attribute__ ((packed))
+typedef enum {
+ false = 0,
+ true = 1
+} bool;
+
+#include <ch375.h>
+
+struct ch375 {
+ uint16_t io_base;
+ uint16_t sec_size;
+};
+
+static inline uint8_t ch375_read_data(struct ch375 *dev)
+{
+ return inb(dev->io_base + CH375_PORT_DATA);
+}
+
+static inline void ch375_write_data(struct ch375 *dev, uint8_t data)
+{
+ outb(data, dev->io_base + CH375_PORT_DATA);
+}
+
+static inline void ch375_send_command(struct ch375 *dev, enum ch375_command cmd)
+{
+ outb(cmd, dev->io_base + CH375_PORT_CMD);
+}
+
+/// True if interrupt line is currently active (i.e. low)
+static inline bool ch375_poll_interrupt(struct ch375 *dev)
+{
+ return !(inb(dev->io_base + CH375_PORT_CMD) & 0x80);
+}
+
+/** Reads one transfer block (64 bytes max). */
+static uint8_t ch375_read_data_block(struct ch375 *dev, uint8_t *buffer)
+{
+ uint8_t n = ch375_read_data(dev);
+
+ insb(dev->io_base + CH375_PORT_DATA, buffer, n);
+
+ return n;
+}
+
+static void ch375_write_data_block(struct ch375 *dev, uint8_t * buffer, uint8_t n)
+{
+ ch375_write_data(dev, n);
+
+ outsb(dev->io_base + CH375_PORT_DATA, buffer, n);
+}
+
+#endif // CH375LNX_H
diff --git a/linux/chudisk.c b/linux/chudisk.c
new file mode 100644
index 0000000..31dd9e5
--- /dev/null
+++ b/linux/chudisk.c
@@ -0,0 +1,622 @@
+/*
+ * CHU - Module for driving CH375 in disk mode from Linux 2.0/2.2
+ * Copyright (C) 2026 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 2 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/>.
+ */
+
+/** how many different units we can handle, i.e. /dev/chuda, chudb, etc.*/
+#define CHUDISK_MAX_UNITS 1
+/** how many bits of the minor number are dedicated to partition number */
+#define CHUDISK_MINOR_SHIFT 6
+/** how many different partitions we can handle (/dev/chuda1, chuda2, etc.)
+ * which comes from the number of bits available in the minor number. */
+#define CHUDISK_MAX_PARTITIONS (1 << CHUDISK_MINOR_SHIFT)
+/** total number of minors we can handle. */
+#define CHUDISK_MAX_MINORS (CHUDISK_MAX_UNITS << CHUDISK_MINOR_SHIFT)
+
+/* module parameters */
+/** configurable major number */
+/* by default, reuse the one from Mylex DAC960 PCI RAID controller; sixth controller" */
+static short major = 53;
+/** IO base */
+static short io = 0x260;
+/** skip device proving */
+static char force = 0;
+/** configurable block size */
+static int softblk = 1024;
+
+/* for blk.h: (needs to be defined before blk.h/lnxabs.h is included) */
+#define MAJOR_NR major
+#define DEVICE_NAME "CH375"
+#define DEVICE_REQUEST do_chudisk_request
+#define DEVICE_NR(device) (MINOR(device)>>CHUDISK_MINOR_SHIFT)
+#define DEVICE_ON(device)
+#define DEVICE_OFF(device)
+
+#include "lnxabs.h"
+
+#include "ch375lnx.h"
+#include <scsi.h>
+
+#define ENABLE_DLOG 0
+#if ENABLE_DLOG
+#define dprintf(args...) printk(KERN_DEBUG args)
+#else
+#define dprintf(args...) /**/
+#endif
+
+MODULE_PARM(major, "h");
+MODULE_PARM_DESC(major, "major device number");
+
+MODULE_PARM(io, "h");
+MODULE_PARM_DESC(io, "io base");
+
+MODULE_PARM(force, "b");
+MODULE_PARM_DESC(force, "force loading, skip probing");
+
+MODULE_PARM(softblk, "i");
+MODULE_PARM_DESC(softblk, "\"soft\" block size in bytes");
+
+MODULE_DESCRIPTION("CHUDISK CH375 USB disk driver");
+MODULE_LICENSE("GPL");
+
+/** hd_structs, used by gendisk to keep track of partition sizes, start points, etc. */
+static struct hd_struct chudisk_structs[CHUDISK_MAX_MINORS];
+/** used by blk layer to keep track of size of every (minor) blk device. */
+static int chudisk_sizes[CHUDISK_MAX_MINORS] = {0};
+/** the block size (possibly different for every minor, but we really don't do that) */
+static int chudisk_blksizes[CHUDISK_MAX_MINORS] = {0};
+/** hardware block size */
+static int chudisk_hardsectsizes[CHUDISK_MAX_MINORS] = {0};
+/** count open for each unit (not partition) */
+static int chudisk_open_count[CHUDISK_MAX_UNITS] = {0};
+/** driver data for each unit */
+static struct ch375 chudisk_dev[CHUDISK_MAX_UNITS];
+
+/** main gendisk layer data structure, which handles partioning */
+static struct gendisk chudisk_gendisk = {
+ .major_name = "chud",
+ .minor_shift = CHUDISK_MINOR_SHIFT,
+ .max_p = CHUDISK_MAX_PARTITIONS,
+ .max_nr = CHUDISK_MAX_UNITS,
+ .part = chudisk_structs,
+ .sizes = chudisk_sizes
+};
+
+/** Waits for the CH375 to generate an interrupt, returns result value.
+ * @param timeout max jiffies to wait for an interrupt */
+static uint8_t wait_for_ch375_result(struct ch375 *dev, unsigned int timeout)
+{
+ unsigned long start = jiffies;
+ unsigned long expire = start + timeout;
+
+ while (!ch375_poll_interrupt(dev) && time_before(jiffies, expire)) {
+
+ }
+
+ if (ch375_poll_interrupt(dev)) {
+ uint8_t status = ch375_get_int_status(dev);
+ return status;
+ } else {
+ printk(KERN_WARNING "chudisk: interrupt timeout! waited %lu jiffies\n", jiffies - start);
+ return 0;
+ }
+}
+
+/** Gets (error) sense data from the USB device (if any)
+ * and prints it to the log. */
+static void disk_sense_error(struct ch375 *dev)
+{
+ char buffer[CH375_BLOCK_SIZE];
+ unsigned len;
+
+ ch375_send_command(dev, CH375_DISK_R_SENSE);
+ if (wait_for_ch375_result(dev, 1000) != CH375_INT_USB_SUCCESS) {
+ printk(KERN_NOTICE "chudisk: could not sense error\n");
+ return;
+ }
+
+ ch375_send_command(dev, CH375_RD_USB_DATA);
+ len = ch375_read_data_block(dev, &buffer[0]);
+
+ printk(KERN_NOTICE "chudisk: sense read data block len = %d\n", len);
+ printk(KERN_NOTICE "chudisk: sense packet %hx %hx %hx %hx %hx %hx %hx %hx\n",
+ buffer[0], buffer[1], buffer[2], buffer[3],
+ buffer[4], buffer[5], buffer[6], buffer[7]);
+ printk(KERN_NOTICE "chudisk: %hx %hx %hx %hx %hx %hx %hx %hx\n",
+ buffer[8], buffer[9], buffer[10], buffer[11],
+ buffer[12], buffer[13], buffer[14], buffer[15]);
+}
+
+/** Read N sectors from the disk
+ * @return number of sectors read
+ */
+static unsigned disk_read_sectors(struct ch375 *dev, uint8_t *buffer, uint32_t lba, uint8_t num)
+{
+ uint8_t result;
+ unsigned total = 0; // Total bytes written
+
+ dprintf("disk read buf=%p sector=%lu num=%hu\n", buffer, lba, num);
+
+ // Initiate read operation
+ ch375_disk_read(dev, lba, num);
+
+ // Now keep going as long as ch375 says to keep going
+ while ((result = wait_for_ch375_result(dev, 1000)) == CH375_INT_USB_DISK_READ) {
+ unsigned len;
+
+ // Sanity check
+ if (total / dev->sec_size >= num) {
+ dprintf("read overflow\n");
+ break;
+ }
+
+ ch375_send_command(dev, CH375_RD_USB_DATA);
+ len = ch375_read_data_block(dev, &buffer[total]);
+
+ //dprintf(" disk read data block len = %d\n", len);
+
+ if (len == 0) {
+ // Done?
+ break;
+ }
+
+ total += len;
+
+ // Continue
+ ch375_send_command(dev, CH375_DISK_RD_GO);
+ }
+
+ dprintf(" disk read total bytes = %u, sectors = %u, final status = 0x%hx\n",
+ total, total / dev->sec_size, result);
+
+ if (result == CH375_INT_USB_DISK_ERR) {
+ disk_sense_error(dev);
+ }
+
+ return total / dev->sec_size;
+}
+
+/** Write N sectors to the disk
+ * @return number of sectors written
+ */
+static unsigned disk_write_sectors(struct ch375 *dev, uint8_t *buffer, uint32_t lba, uint8_t num)
+{
+ uint8_t result;
+ unsigned total = 0; // Total bytes written
+
+ dprintf("disk write buf=%p sector=%lu num=%hu\n", buffer, lba, num);
+
+ // Initiate operation
+ ch375_disk_write(dev, lba, num);
+
+ // Now keep going as long as ch375 says to keep going
+ while ((result = wait_for_ch375_result(dev, 1000)) == CH375_INT_USB_DISK_WRITE) {
+ const unsigned len = CH375_BLOCK_SIZE;
+
+ // Sanity check
+ if (total / dev->sec_size >= num) {
+ dprintf("write overflow\n");
+ break;
+ }
+
+ ch375_send_command(dev, CH375_WR_USB_DATA7);
+ ch375_write_data_block(dev, &buffer[total], len);
+
+ total += len;
+
+ // Continue
+ ch375_send_command(dev, CH375_DISK_WR_GO);
+ }
+
+ dprintf(" disk write total bytes = %u, sectors = %u, final status = 0x%hx\n",
+ total, total / dev->sec_size, result);
+
+ if (result == CH375_INT_USB_DISK_ERR) {
+ disk_sense_error(dev);
+ }
+
+ return total / dev->sec_size;
+}
+
+/** Enables CH375 USB host mode, initializes "disk" mode on it
+ * and connects to a disk.
+ * @return true if connected
+ */
+static bool chudisk_connect(struct ch375 *dev)
+{
+ int tries;
+
+ dprintf("switch to usb host\n");
+ ch375_set_usb_mode(dev, CH375_USB_MODE_HOST_AUTO_SOF);
+ if (wait_for_ch375_result(dev, HZ / 5) != CH375_INT_USB_CONNECT) {
+ printk(KERN_INFO "chudisk: could not turn on usb host, likely no device\n");
+ return false;
+ }
+
+ dprintf("try init disk\n");
+ ch375_disk_init(dev);
+ if (wait_for_ch375_result(dev, HZ) != CH375_INT_USB_SUCCESS) {
+ printk(KERN_INFO "chudisk: could not init disk\n");
+ 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(dev);
+ if (wait_for_ch375_result(dev, HZ) == CH375_INT_USB_SUCCESS) {
+ dprintf("disk now init\n");
+ return true;
+ }
+ mdelay(50);
+ }
+
+ printk(KERN_INFO "chudisk: disk failed to init\n");
+ return false;
+}
+
+/** Tests if there is still a disk connected and initialized. */
+static bool is_chudisk_ready(struct ch375 *dev)
+{
+ ch375_test_disk_ready(dev);
+ if (wait_for_ch375_result(dev, HZ/10) == CH375_INT_USB_SUCCESS) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/** Actual main I/O function, called whenever there are pending I/O requests */
+static void do_chudisk_request(void)
+{
+ while (CURRENT) {
+ int rc = 0;
+ struct hd_struct *hd_struct;
+ INIT_REQUEST;
+
+ hd_struct = &chudisk_structs[MINOR(CURRENT->rq_dev)];
+
+ dprintf("do_chudisk_request: cmd %u sector %lu(/%lu) nr_sectors %lu/%lu\n",
+ CURRENT->cmd, CURRENT->sector, hd_struct->nr_sects, CURRENT->current_nr_sectors, CURRENT->nr_sectors);
+
+ // If:
+ // the request is for one of our devices
+ // and it is in-range of the corresponding partition
+ // and respects our max_nr_sectors (256)
+
+ if (CURRENT_DEV < CHUDISK_MAX_UNITS
+ && CURRENT->sector + CURRENT->nr_sectors <= hd_struct->nr_sects
+ && CURRENT->current_nr_sectors <= 0xff) {
+ struct ch375 *dev = &chudisk_dev[CURRENT_DEV];
+ uint32_t sector = CURRENT->sector + hd_struct->start_sect;
+ uint8_t cnt = CURRENT->current_nr_sectors;
+
+ switch (CURRENT->cmd) {
+ case READ:
+ if (disk_read_sectors(dev, CURRENT->buffer, sector, cnt) == cnt) {
+ rc = true; // success
+ }
+ break;
+ case WRITE:
+ if (disk_write_sectors(dev, CURRENT->buffer, sector, cnt) == cnt) {
+ rc = true; // success
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ end_request(rc);
+ }
+}
+
+/** Called by gendisk to check if removable media may have changed since last time.
+ * If so, gendisk will invalidate buffers and eventually call revalidate.
+ * @return true if media may have changed
+ */
+ static int chudisk_check_media_change(kdev_t dev)
+{
+ int unit = DEVICE_NR(dev);
+ bool ready;
+
+ dprintf("chudisk_check_media_change: unit=%d\n", unit);
+
+ if (unit >= CHUDISK_MAX_UNITS) {
+ return -ENODEV;
+ }
+
+ ready = is_chudisk_ready(&chudisk_dev[unit]);
+
+ dprintf("chudisk_check_media_change: unit=%d disk_ready=%d\n", unit, ready);
+
+ // if CH375 is not ready, it means device was unplugged (or never configured to begin with)
+ // assume media changed
+ return !ready;
+}
+
+/** Revalidate means drop all buffers (assuming media changed)
+ * reinitialize all media information (sizes, etc.)
+ * and re-read partition tables. */
+static int chudisk_revalidate(kdev_t devno)
+{
+ int unit = DEVICE_NR(devno);
+ struct ch375 *dev = &chudisk_dev[unit];
+ uint32_t blk_size;
+ uint32_t sector_cnt;
+ char buffer[CH375_BLOCK_SIZE];
+ int p;
+
+ dprintf("chudisk_revalidate: unit=%d\n", unit);
+
+ if (unit >= CHUDISK_MAX_UNITS) {
+ return -ENODEV;
+ }
+
+ if (!is_chudisk_ready(dev)) {
+ if (!chudisk_connect(dev)) {
+ return -ENOMEDIUM;
+ }
+ }
+
+ ch375_send_command(dev, CH375_DISK_SIZE);
+ if (wait_for_ch375_result(dev, HZ / 10) != CH375_INT_USB_SUCCESS) {
+ return -EIO;
+ }
+
+ ch375_send_command(dev, CH375_RD_USB_DATA);
+ if (ch375_read_data_block(dev, buffer) < 8) {
+ printk(KERN_WARNING "chudisk: disk size command return less than 8 bytes");
+ return -EIO;
+ }
+
+ blk_size = ntohl(*(uint32_t*)&buffer[4]);
+ sector_cnt = ntohl(*(uint32_t*)&buffer[0]);
+
+ dprintf("chudisk: sector size %u num sectors %u\n", blk_size, sector_cnt);
+
+ // TODO Support non-default block_size
+ dev->sec_size = blk_size;
+ ch375_write_reg(dev, CH375_REG_DISK_PKT_P_SEC, blk_size / CH375_BLOCK_SIZE);
+
+ // Fill in the struct for the raw device, so that we can answer I/O requests on it
+ chudisk_structs[unit << CHUDISK_MINOR_SHIFT].nr_sects = sector_cnt;
+
+ // For all minors associated to this unit
+ for (p = 0; p < CHUDISK_MAX_PARTITIONS; p++) {
+ int minor = (unit << CHUDISK_MINOR_SHIFT) + p;
+ kdev_t mdevno = MKDEV(MAJOR_NR, minor);
+
+ fsync_dev(mdevno);
+ invalidate_inodes_dev(mdevno);
+ invalidate_buffers(mdevno);
+
+ chudisk_hardsectsizes[minor] = blk_size;
+ }
+
+ resetup_one_dev(&chudisk_gendisk, unit);
+
+ return 0;
+}
+
+/** Fill in guessed hd_geometry from the current media size. */
+static int chudisk_get_geom(kdev_t devno, struct hd_geometry *g)
+{
+ struct hd_struct *hd_struct = &chudisk_structs[DEVICE_NR(devno)];
+ struct hd_struct *hd_struct_part = &chudisk_structs[MINOR(devno)];
+ unsigned int cylinders, heads, sectors;
+
+ if (hd_struct->nr_sects == 0 || hd_struct_part->nr_sects == 0) {
+ return -ENOMEDIUM;
+ }
+
+ guess_geometry(hd_struct->nr_sects, &cylinders, &heads, &sectors);
+ g->cylinders = cylinders > 0xFFFF ? 0xFFFF : cylinders;
+ g->heads = heads;
+ g->sectors = sectors;
+ g->start = hd_struct_part->start_sect;
+
+ return 0;
+}
+
+/** Ioctl handling. */
+static int chudisk_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int err;
+
+ dprintf("chudisk_ioctl: unit=%d cmd=%u arg=%lu\n", DEVICE_NR(inode->i_rdev), cmd, arg);
+
+ if (DEVICE_NR(inode->i_rdev) >= CHUDISK_MAX_UNITS) {
+ return -ENXIO;
+ }
+
+ switch (cmd) {
+ case HDIO_GETGEO: {
+ struct hd_geometry g;
+ if (!arg) return -EINVAL;
+ if ((err = chudisk_get_geom(inode->i_rdev, &g))) {
+ return err;
+ }
+ return copy_to_user((struct hd_geometry*)arg, &g, sizeof g) ? -EFAULT : 0;
+ }
+ case BLKRASET:
+ if (!is_sys_admin()) return -EACCES;
+ if (arg > 0xff) return -EINVAL;
+ read_ahead[MAJOR(inode->i_rdev)] = arg;
+ return 0;
+ case BLKRAGET:
+ if (!arg) return -EINVAL;
+ return put_user(read_ahead[MAJOR(inode->i_rdev)], (long *)arg);
+ case BLKGETSIZE:
+ if (!arg) return -EINVAL;
+ return put_user(chudisk_structs[MINOR(inode->i_rdev)].nr_sects, (long*)arg);
+ case BLKFLSBUF:
+ if (!is_sys_admin()) return -EACCES;
+ fsync_dev(inode->i_rdev);
+ invalidate_buffers(inode->i_rdev);
+ return 0;
+ case BLKRRPART:
+ if (!is_sys_admin()) return -EACCES;
+ return chudisk_revalidate(inode->i_rdev);
+ RO_IOCTLS(inode->i_rdev, arg);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int chudisk_open(struct inode *inode, struct file *file)
+{
+ int unit = DEVICE_NR(inode->i_rdev);
+ dprintf("chudisk_open: unit=%d\n", unit);
+ if (unit >= CHUDISK_MAX_UNITS) {
+ return -ENXIO;
+ }
+
+ MOD_INC_USE_COUNT;
+
+ if (chudisk_open_count[unit] == 0) {
+ // First time open
+ dprintf("chudisk: first open\n");
+ // Ask gendisk to check if media may have changed (which may call revalidate)
+ check_disk_change(inode->i_rdev);
+ // if we still have no media, refuse open() call
+ if (!is_chudisk_ready(&chudisk_dev[unit])) {
+ MOD_DEC_USE_COUNT;
+ return -ENOMEDIUM;
+ }
+ }
+
+ chudisk_open_count[unit]++;
+
+ return 0;
+}
+
+static int chudisk_release(struct inode *inode, struct file *file)
+{
+ int unit = DEVICE_NR(inode->i_rdev);
+
+ dprintf("chudisk_release: unit=%d\n", unit);
+
+ if (unit >= CHUDISK_MAX_UNITS) {
+ return -ENXIO;
+ }
+
+ chudisk_open_count[unit]--;
+ MOD_DEC_USE_COUNT;
+
+ return 0;
+}
+
+static struct file_operations blk_fops = {
+ .read = block_read,
+ .write = block_write,
+ .ioctl = chudisk_ioctl,
+ .open = chudisk_open,
+ .release = chudisk_release,
+ .check_media_change = chudisk_check_media_change,
+ .revalidate = chudisk_revalidate
+};
+
+static __init int chudisk_init(void)
+{
+ // TODO Here we are currently assuming one unit
+ // But it would be trivial to add support for more.
+ const int unit = 0;
+ struct ch375 *dev = &chudisk_dev[unit];
+ int i;
+
+ dev->io_base = io;
+ dev->sec_size = 512;
+
+ if (check_region(io, 2)) {
+ printk(KERN_WARNING "chudisk: ports at 0x%x not available\n", io);
+ return -ENXIO;
+ }
+
+ request_region(io, 2, "chudisk");
+
+ if (!force) {
+ dprintf("chudisk: resetting CH375 at 0x%hx\n", dev->io_base);
+ ch375_reset(dev);
+ mdelay(100);
+ if (ch375_check_exists(dev, 0xA5) != (uint8_t)~0xA5) {
+ printk(KERN_WARNING "chudisk: CH375 not detected at 0x%hx (failed CHECK_EXISTS), change io= param\n", dev->io_base);
+ release_region(io, 2);
+ return -ENODEV;
+ }
+ }
+
+ if (register_blkdev(MAJOR_NR, "chud", &blk_fops)) {
+ printk(KERN_WARNING "chudisk: unable to get major number %d\n", MAJOR_NR);
+ release_region(io, 2);
+ return -ENXIO;
+ }
+
+ printk(KERN_INFO "chudisk: found CH375 v.%x at 0x%hx, using major %d\n",
+ ch375_get_version(dev), dev->io_base, MAJOR_NR);
+
+ // Set defaults for unit
+ for (i = 0; i < CHUDISK_MAX_PARTITIONS; i++) {
+ const int minor = (unit << CHUDISK_MINOR_SHIFT) + i;
+ chudisk_sizes[minor] = 0;
+ chudisk_blksizes[minor] = softblk;
+ chudisk_hardsectsizes[minor] = dev->sec_size;
+ }
+
+ // Set defaults for everything else
+ read_ahead[MAJOR_NR] = 8; // default to 8 sector read-ahead
+
+ blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+ blk_size[MAJOR_NR] = chudisk_sizes;
+ blksize_size[MAJOR_NR] = chudisk_blksizes;
+ hardsect_size[MAJOR_NR] = chudisk_hardsectsizes;
+
+ chudisk_gendisk.major = major;
+ chudisk_gendisk.nr_real = 1;
+ chudisk_gendisk.next = gendisk_head;
+ gendisk_head = &chudisk_gendisk;
+
+ chudisk_revalidate(MKDEV(MAJOR_NR, unit << CHUDISK_MINOR_SHIFT));
+
+ return 0;
+}
+
+static void chudisk_exit(void)
+{
+ struct gendisk **gdp;
+
+ unregister_blkdev(MAJOR_NR, "chud");
+ release_region(io, 2);
+
+ for(gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next)) {
+ if (*gdp == &chudisk_gendisk) {
+ *gdp = (*gdp)->next;
+ break;
+ }
+ }
+
+ blk_dev[MAJOR_NR].request_fn = NULL;
+ blk_size[MAJOR_NR] = NULL;
+ blksize_size[MAJOR_NR] = NULL;
+ hardsect_size[MAJOR_NR] = NULL;
+
+ dprintf("chudisk: bye\n");
+}
+
+
+module_init(chudisk_init);
+module_exit(chudisk_exit);
diff --git a/linux/lnxabs.h b/linux/lnxabs.h
new file mode 100644
index 0000000..47adcea
--- /dev/null
+++ b/linux/lnxabs.h
@@ -0,0 +1,139 @@
+/*
+ * Abstractions between different versions of Linux
+ * Currently only tested amonst the latest versions of 2.0 and 2.2
+ * Copyright (C) 2026 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 2 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/>.
+ */
+
+#ifdef MODVERSIONS
+#include <linux/modversions.h>
+#endif
+
+#include <linux/version.h>
+
+#define MAKE_VERSION_CODE(vers, rel, seq) ( (vers)<<16 | (rel)<<8 | (seq) )
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+// Module entry point abstraction
+#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,36)
+ #define __init /**/
+ #define module_init(x) int init_module(void) { return x(); }
+ #define module_exit(x) void cleanup_module(void) { x(); }
+#else
+ #include <linux/init.h>
+#endif
+
+// Module params
+#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,26)
+ #define MODULE_PARM(var, type) /**/
+ #define MODULE_PARM_DESC(var, s) /**/
+ #define MODULE_DESCRIPTION(s) /**/
+#endif
+#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,4,0)
+ #define MODULE_LICENSE(s) /**/
+#endif
+
+#include <linux/ioport.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+
+#include <linux/blk.h>
+#include <linux/blkdev.h>
+#include <linux/hdreg.h>
+
+#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,0)
+#define invalidate_inodes_dev(devno) invalidate_inodes(devno)
+#else
+static inline void invalidate_inodes_dev(kdev_t devno) {
+ struct super_block *sb = get_super(devno);
+ if (sb) invalidate_inodes(sb);
+}
+#endif
+
+#include <linux/delay.h>
+
+// time before/after
+#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,95)
+ #define time_after(a,b) ((long)(b) - (long)(a) < 0)
+ #define time_before(a,b) time_after(b,a)
+#endif
+
+// mdelay
+#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,0)
+static inline void mdelay(int milliseconds)
+{
+ int i;
+ for(i=0; i<milliseconds; i++)
+ udelay(1000);
+}
+#endif
+
+// put_user/copy_to_user
+#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,0)
+ #undef put_user
+ #undef get_user
+
+ #define put_user(x,ptr) ({ \
+ int i = verify_area(VERIFY_WRITE, (ptr), sizeof(*(ptr))); \
+ if (i == 0) { __put_user((unsigned long)(x),(ptr),sizeof(*(ptr))); } \
+ i; \
+ })
+ #define get_user(x,ptr) ({ \
+ int i = verify_area(VERIFY_READ, (ptr), sizeof(*(ptr))); \
+ if (i == 0) { x = ((__typeof__(*(ptr)))__get_user((ptr),sizeof(*(ptr)))); } \
+ i; \
+ })
+
+ extern inline int copy_from_user(void *to, const void *from, unsigned long n)
+ {
+ int i = verify_area(VERIFY_READ, from, n);
+ if (i)
+ return i;
+ memcpy_fromfs(to, from, n);
+ return 0;
+ }
+
+ extern inline int copy_to_user(void *to, const void *from, unsigned long n)
+ {
+ int i = verify_area(VERIFY_WRITE, to, n);
+ if (i)
+ return i;
+ memcpy_tofs(to, from, n);
+ return 0;
+ }
+#else
+ #include <asm/uaccess.h>
+#endif
+
+// capabilities
+#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,0)
+#define is_sys_admin() suser()
+#else
+#define is_sys_admin() capable(CAP_SYS_ADMIN)
+#endif
+
+// stdtypes
+#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,0)
+ typedef __s8 int8_t;
+ typedef __s16 int16_t;
+ typedef __s32 int32_t;
+ typedef __u8 uint8_t;
+ typedef __u16 uint16_t;
+ typedef __u32 uint32_t;
+#endif
+
+#include <asm/byteorder.h>
diff --git a/linux/makefile b/linux/makefile
new file mode 100644
index 0000000..f293cc1
--- /dev/null
+++ b/linux/makefile
@@ -0,0 +1,28 @@
+MODDIR := /lib/modules/$(shell uname -r)
+
+INCLUDE := -isystem $(MODDIR)/build/include -isystem ../inc
+CFLAGS := -O2 -D__KERNEL__ -DMODULE -Wall $(INCLUDE)
+ifneq (,$(wildcard $(MODDIR)/build/include/linux/modversions.h))
+CFLAGS += -DMODVERSIONS
+endif
+
+CC ?= gcc
+
+chudisk.o: chudisk.c lnxabs.h ch375lnx.h
+
+.PHONY: clean install uninstall test
+clean:
+ rm -rf *.o
+
+install: chudisk.o
+ install -m0644 chudisk.o $(MODDIR)/block
+ depmod -A
+
+uninstall:
+ rm -f $(MODDIR)/block/chudisk.o
+
+test: chudisk.o
+ if lsmod | grep chudisk; then rmmod chudisk; fi
+ dmesg -n 8
+ dmesg -c > /dev/null
+ insmod chudisk.o force=1
diff --git a/linux/mkdev.sh b/linux/mkdev.sh
new file mode 100755
index 0000000..d1393e6
--- /dev/null
+++ b/linux/mkdev.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+major=53
+if [ $# -gt 0 ]; then
+ major=$1
+fi
+
+prefix=/dev/chuda
+if [ $# -gt 1 ]; then
+ prefix=$2
+fi
+
+rm -f ${prefix}*
+mknod "${prefix}" b $major 0
+for i in $(seq 1 20); do
+ mknod "${prefix}${i}" b $major $i
+done
+
+chown root:disk ${prefix}*
diff --git a/makefile b/makefile
index a42e957..c7cc4db 100644
--- a/makefile
+++ b/makefile
@@ -1,67 +1,45 @@
#!/usr/bin/env wmake
-# All binaries to build
-bins = test.exe chudisk.sys chud.exe
+subdirs=dos w9x
-# Object files for chdisk
-chudisk_objs = chudisk.obj chudinit.obj cio.obj
-
-# Compiler arguments for DOS
-dos_cflags = -bt=dos -3 -ms -osi -ox -w3 -wcd=202 -g=DGROUP
-# -ms to use small memory model (this assumes ss == ds)
-# -osi to optimize for size, put intrinsics inline (to avoid runtime calls)
-# -ox extra optimization (impact is not very big)
-# -w3 enables warnings
-# -wcd=202 disables the unreferenced function warning (e.g., for inline functions in headers)
-
-# Compiler arguments for the DOS drivers
-dosdrv_cflags = -bt=dos -3 -ms -zu -s -osi -ox -w3 -wcd=202 -g=DGROUP
-# -zu since ss != ds in the driver
-# -s to disable stack checks, since it inserts calls to the runtime
-
-# Full compiler command lines
-compile_dos = *wcc -fo=$^@ $(dos_cflags) $[@
-compile_dosdrv = *wcc -fo=$^@ $(dosdrv_cflags) $[@
-
-# Object files
-
-.BEFORE:
- # We need DOS and Windows headers, not host platform's
- set include=$(%watcom)/h/win;$(%watcom)/h
-
-all: $(bins) .SYMBOLIC
-
-test.exe: test.obj .AUTODEPEND
- *wlink system dos name $@ file { test.obj }
-
-test.obj: test.c .AUTODEPEND
- *wcc -fo=$^@ -bt=dos -3 -osi -w3 -wcd=202 $[@
-
-chudisk.sys: chudisk.lnk $(chudisk_objs) .AUTODEPEND
- *wlink @$[@ name $@ file { $(chudisk_objs) }
-
-chudisk.obj: chudisk.c .AUTODEPEND
- $(compile_dosdrv)
-
-chudinit.obj: chudinit.c .AUTODEPEND
- $(compile_dosdrv)
-
-cio.obj: cio.c .AUTODEPEND
- $(compile_dosdrv)
-
-chud.exe: chud.obj .AUTODEPEND
- *wlink name $@ system dos file { chud.obj }
-
-chud.obj: chud.c .AUTODEPEND
- $(compile_dos)
+all: .SYMBOLIC
+ for %i in ($(subdirs)) do %make %i/$^@
clean: .SYMBOLIC
- rm -f $(bins) *.obj
+ for %i in ($(subdirs)) do %make %i/$^@
+
+# This would be so much easier with a multi-line macro...
+sd1=cd $^:
+!ifdef debug
+sd2=wmake $^. debug=$(debug)
+!else
+sd2=wmake $^.
+!endif
+sd3=cd ..
+
+dos/all: .SYMBOLIC
+ $(sd1)
+ $(sd2)
+ $(sd3)
+dos/clean: .SYMBOLIC
+ $(sd1)
+ $(sd2)
+ $(sd3)
+w9x/all: .SYMBOLIC
+ $(sd1)
+ $(sd2)
+ $(sd3)
+w9x/clean: .SYMBOLIC
+ $(sd1)
+ $(sd2)
+ $(sd3)
# Build a zip with the driver binaries
-zip: $(bins) .SYMBOLIC
- zip --DOS-names -fz- chu.zip $(bins)
+zip: dos/all w9x/all .SYMBOLIC
+ zip --DOS-names -fz- chu.zip dos/chudisk.sys dos/chud.exe w9x/chudisk.pdr w9x/chudisk.inf
srczip: .SYMBOLIC
- zip -fz- chusrc.zip COPYING README.md makefile *.lnk *.h *.c inc/*.h
-
+ zip -fz- chusrc.zip COPYING README.md makefile inc/*.h &
+ dos/makefile dos/*.c dos/*.h dos/*.lnk &
+ w9x/makefile w9x/*.c w9x/*.h w9x/*.lnk w9x/*.inf w9x/inc/*.h &
+ linux/makefile linux/*.c linux/*.h linux/*.sh
diff --git a/w9x/ch375vxd.h b/w9x/ch375vxd.h
new file mode 100644
index 0000000..3d3db26
--- /dev/null
+++ b/w9x/ch375vxd.h
@@ -0,0 +1,74 @@
+/*
+ * CHU - IO routines to CH375 from a IOS VxD
+ * Copyright (C) 2024-2026 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/>.
+ */
+
+#ifndef CH375VXD_H
+#define CH375VXD_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <conio.h>
+#include <ch375.h>
+
+struct ch375 {
+ uint16_t io_base;
+};
+
+static inline uint8_t ch375_read_data(struct ch375 *dev)
+{
+ return inp(dev->io_base + CH375_PORT_DATA);
+}
+
+static inline void ch375_write_data(struct ch375 *dev, uint8_t data)
+{
+ outp(dev->io_base + CH375_PORT_DATA, data);
+}
+
+static inline void ch375_send_command(struct ch375 *dev, enum ch375_command cmd)
+{
+ outp(dev->io_base + CH375_PORT_CMD, cmd);
+}
+
+/// True if interrupt line is currently active (i.e. low)
+static inline bool ch375_poll_interrupt(struct ch375 *dev)
+{
+ return !(inp(dev->io_base + CH375_PORT_CMD) & 0x80);
+}
+
+
+/** Reads one transfer block (64 bytes max). */
+// Watcom does not seem able to synthesize rep ins/outs. Do it in 286+ mode.
+static uint8_t ch375_read_data_block(struct ch375 *dev, uint8_t far *buffer);
+#pragma aux ch375_read_data_block = \
+ "mov dx, [ebx]" \
+ "in al, dx" \
+ "movzx ecx, al" \
+ "rep ins byte ptr [edi], dx" \
+ __parm [ebx] [edi] \
+ __value [al] \
+ __modify exact [ecx dx edi]
+
+static void ch375_write_data_block(struct ch375 *dev, uint8_t far *buffer, uint8_t n);
+#pragma aux ch375_write_data_block = \
+ "mov dx, [ebx]" \
+ "out dx, al" \
+ "movzx ecx, al" \
+ "rep outs dx, byte ptr [esi]" \
+ __parm [ebx] [esi] [al] \
+ __modify exact [ecx dx esi]
+
+#endif // CH375VXD_H
diff --git a/w9x/chudisk.inf b/w9x/chudisk.inf
new file mode 100644
index 0000000..e5985ec
--- /dev/null
+++ b/w9x/chudisk.inf
@@ -0,0 +1,45 @@
+[Version]
+Signature=$CHICAGO$
+Class=hdc
+Provider=%CHUDISK%
+
+[DestinationDirs]
+DefaultDestDir=12
+
+[Manufacturer]
+%CHUDISK%=CHUDISK
+
+[CHUDISK]
+%CH375%=CHUDISK.Install,*CHUDISK
+
+[CHUDISK.Install]
+CopyFiles=CHUDISK.CopyFiles
+AddReg=CHUDISK.AddReg
+LogConfig=CHUDISK_NoIRQ,CHUDISK_IRQ
+
+[CHUDISK.CopyFiles]
+chudisk.pdr
+
+[CHUDISK.AddReg]
+HKR,,DevLoader,0,*IOS
+HKR,,PortDriver,0,chudisk.pdr
+
+[CHUDISK_NoIRQ]
+ConfigPriority=HARDWIRED
+; Allow any two ports between 200-3FF
+IOConfig=2@200-3FF
+
+[CHUDISK_IRQ]
+ConfigPriority=HARDWIRED
+IOConfig=2@200-3FF
+IRQConfig=2,5,7,9
+
+[SourceDisksNames]
+1=CHUDISK Driver Disk,CHUD,0000-0000
+
+[SourceDisksFiles]
+chudisk.pdr=1
+
+[Strings]
+CHUDISK="CHUDISK"
+CH375="CH375 USB Disk Controller"
diff --git a/w9x/chudisk.lnk b/w9x/chudisk.lnk
new file mode 100644
index 0000000..0bfa393
--- /dev/null
+++ b/w9x/chudisk.lnk
@@ -0,0 +1,8 @@
+system win_vxd dynamic
+
+option statics
+option map
+option modname=CHUDISK
+option description 'CHUDISK CH375 disk port driver'
+
+export vxd_ddb.1
diff --git a/w9x/chupdr.c b/w9x/chupdr.c
new file mode 100644
index 0000000..df6c8bd
--- /dev/null
+++ b/w9x/chupdr.c
@@ -0,0 +1,816 @@
+/*
+ * CHU - This is a IOS port driver for the CH375 in disk mode
+ * Copyright (C) 2026 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 <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <scsi.h>
+
+#include "inc/vxd.h"
+#include "inc/vmm.h"
+#include "inc/configmg.h"
+#include "inc/ios.h"
+
+#include "ch375vxd.h"
+#include "utils.h"
+#include "dlog.h"
+
+int control(uint32_t message, uint32_t ebx, uint32_t ecx, uint32_t edx, uint32_t esi, uint32_t edi);
+#pragma aux control "*" parm routine [eax] [ebx] [ecx] [edx] [esi] [edi] value [eax] modify []
+
+void aer(AEP *aep);
+#pragma aux (__cdecl) aer
+
+void ior(IOP *iop);
+#pragma aux (__cdecl) ior
+
+/** Note: due to vxdldr limitations, we can't have DATA segments here.
+ * Put all static/global variables in the code segment! */
+
+ILB __based(__segname("_CODE")) driver_ilb = {0};
+
+DRP __based(__segname("_CODE")) driver_drp = {
+ .DRP_eyecatch_str = "CHUDXXXX",
+ .DRP_LGN = DRP_MISC_PD,
+ .DRP_aer = aer,
+ .DRP_ilb = (ILB*) &driver_ilb,
+ .DRP_ascii_name = "CHUDISK Port"
+};
+
+VxD_Desc_Block __based(__segname("_CODE")) vxd_ddb = {
+ .sdk_version = SDK_VERSION_4,
+ .req_device_number = UNDEFINED_DEVICE_ID,
+ .dev_major_version = 1,
+ .dev_minor_version = 0,
+ .name = "CHUDISK ",
+ .init_order = UNDEFINED_INIT_ORDER,
+ .control_proc = control,
+ .reference_data = (void*) &driver_drp,
+ .prev = DDB_PREV_INIT,
+ .size = sizeof(VxD_Desc_Block),
+ .reserved = {0}
+};
+#pragma aux vxd_ddb "*" // must be exported
+
+/** Keeps track of how many devices we are managing. Usually 0 or 1.
+ * If 0, we'll try to unload ourselves. */
+static unsigned short __based(__segname("_CODE")) managed_devices = 0;
+
+/** Our DDB structure, which contains all our private fields. */
+typedef struct CHUDISK_DDB {
+ DDB ddb;
+ struct ch375 dev;
+ enum {
+ /** Initial state -- not doing anything. */
+ STATE_IDLE = 0,
+ STATE_CONNECTED
+ } state;
+ HTIMEOUT timeout;
+ unsigned sector_size;
+} CHUDISK_DDB;
+
+#if ENABLE_DLOG && DLOG_TARGET == DLOG_TARGET_UNDEF
+// Outputs DLOG to the IOS debug log
+/** Initializes the debug log port. */
+static void dlog_init() { }
+
+/** Logs a single character to the debug message IO port. */
+static inline void dputc(char c) {
+ if (driver_ilb.ILB_dprintf) {
+ if (c == '\n') {
+ driver_ilb.ILB_dprintf("\n\r");
+ } else {
+ driver_ilb.ILB_dprintf("%c", &c);
+ }
+ }
+}
+#endif
+
+#if ENABLE_DLOG
+#include "dumpdcb.h"
+#else
+static inline void dcb_dump(DCB *d) { (void)d; }
+#endif
+
+/** Waits for the CH375 to generate an interrupt, returns result value.
+ * @param ms max milliseconds to wait for an interrupt */
+static uint8_t wait_for_ch375_result(struct ch375 *dev, unsigned int ms)
+{
+ uint32_t start_time = Get_Last_Updated_System_Time(), elapsed;
+ while (!ch375_poll_interrupt(dev) &&
+ (elapsed = Get_Last_Updated_System_Time() - start_time) < ms) {
+
+ }
+
+ if (ch375_poll_interrupt(dev)) {
+ uint8_t status = ch375_get_int_status(dev);
+ return status;
+ } else {
+ dprintf("%s: timeout! waited %u ms\n", __func__, elapsed);
+ return 0;
+ }
+}
+
+/** Gets sense/error information and puts it on the debug log. */
+static void disk_sense_error(CHUDISK_DDB *ddb)
+{
+#if ENABLE_DLOG
+ char buffer[CH375_BLOCK_SIZE];
+ unsigned len;
+
+ dprintf("%s\n", __func__);
+
+ ch375_send_command(&ddb->dev, CH375_DISK_R_SENSE);
+ if (wait_for_ch375_result(&ddb->dev, 1000) != CH375_INT_USB_SUCCESS) {
+ dputs("could not sense error");
+ return;
+ }
+
+ ch375_send_command(&ddb->dev, CH375_RD_USB_DATA);
+ len = ch375_read_data_block(&ddb->dev, &buffer[0]);
+
+ dprintf(" sense read data block len = %d\n", len);
+ dprintf(" sense packet %hx %hx %hx %hx %hx %hx %hx %hx\n",
+ buffer[0], buffer[1], buffer[2], buffer[3],
+ buffer[4], buffer[5], buffer[6], buffer[7]);
+ dprintf(" %hx %hx %hx %hx %hx %hx %hx %hx\n",
+ buffer[8], buffer[9], buffer[10], buffer[11],
+ buffer[12], buffer[13], buffer[14], buffer[15]);
+#else
+ (void)ddb;
+#endif
+}
+
+/** Read N sectors from the disk
+ * @return number of sectors read
+ */
+static unsigned disk_read_sectors(CHUDISK_DDB *ddb, uint8_t *buffer, uint32_t lba, uint8_t num)
+{
+ uint8_t result;
+ unsigned total = 0; // Total bytes written
+
+ dprintf("disk read buf=%p sector=%lu num=%hu\n", buffer, lba, num);
+
+ // Initiate read operation
+ ch375_disk_read(&ddb->dev, lba, num);
+
+ // Now keep going as long as ch375 says to keep going
+ while ((result = wait_for_ch375_result(&ddb->dev, 1000)) == CH375_INT_USB_DISK_READ) {
+ unsigned len;
+
+ // Sanity check
+ if (total / ddb->sector_size >= num) {
+ dputs("read overflow");
+ break;
+ }
+
+ ch375_send_command(&ddb->dev, CH375_RD_USB_DATA);
+ len = ch375_read_data_block(&ddb->dev, &buffer[total]);
+
+ //dprintf(" disk read data block len = %d\n", len);
+
+ if (len == 0) {
+ // Done?
+ break;
+ }
+
+ total += len;
+
+ // Continue
+ ch375_send_command(&ddb->dev, CH375_DISK_RD_GO);
+ }
+
+ dprintf(" disk read total bytes = %u, sectors = %u, final status = 0x%hx\n",
+ total, total / ddb->sector_size, result);
+
+ if (result == CH375_INT_USB_DISK_ERR) {
+ disk_sense_error(ddb);
+ }
+
+ return total / ddb->sector_size;
+}
+
+/** Write N sectors to the disk
+ * @return number of sectors written
+ */
+static unsigned disk_write_sectors(CHUDISK_DDB *ddb, uint8_t *buffer, uint32_t lba, uint8_t num)
+{
+ uint8_t result;
+ unsigned total = 0; // Total bytes written
+
+ dprintf("disk write buf=%p sector=%lu num=%hu\n", buffer, lba, num);
+
+ // Initiate operation
+ ch375_disk_write(&ddb->dev, lba, num);
+
+ // Now keep going as long as ch375 says to keep going
+ while ((result = wait_for_ch375_result(&ddb->dev, 1000)) == CH375_INT_USB_DISK_WRITE) {
+ const unsigned len = CH375_BLOCK_SIZE;
+
+ // Sanity check
+ if (total / ddb->sector_size >= num) {
+ dputs("write overflow");
+ break;
+ }
+
+ ch375_send_command(&ddb->dev, CH375_WR_USB_DATA7);
+ ch375_write_data_block(&ddb->dev, &buffer[total], len);
+
+ total += len;
+
+ // Continue
+ ch375_send_command(&ddb->dev, CH375_DISK_WR_GO);
+ }
+
+ dprintf(" disk write total bytes = %u, sectors = %u, final status = 0x%hx\n",
+ total, total / ddb->sector_size, result);
+
+ if (result == CH375_INT_USB_DISK_ERR) {
+ disk_sense_error(ddb);
+ }
+
+ return total / ddb->sector_size;
+}
+
+/** Reinquiries disk size and recomputes the geometry from it.
+ @return true if there was a disk and we could refresh geometry. */
+static bool disk_refresh_geometry(CHUDISK_DDB *ddb, DCB *dcb)
+{
+ char buffer[CH375_BLOCK_SIZE];
+
+ if (ddb->state == STATE_IDLE) {
+ // No disk. Clear all geometry flags.
+ dcb->DCB_cmn.DCB_TSD_Flags &= ~(DCB_TSD_APPARENT_PRE_SET | DCB_TSD_ACTUAL_PRE_SET);
+ return false;
+ }
+
+ dprintf("%s: reading disk size\n", __func__);
+
+ if (ddb->state != STATE_CONNECTED) {
+ dprintf("%s: invalid state\n", __func__);
+ return false;
+ }
+
+ // Ask controller for disk size
+ ch375_send_command(&ddb->dev, CH375_DISK_SIZE);
+ if (wait_for_ch375_result(&ddb->dev, 200) != CH375_INT_USB_SUCCESS) {
+ return false;
+ }
+
+ ch375_send_command(&ddb->dev, CH375_RD_USB_DATA);
+ if (ch375_read_data_block(&ddb->dev, buffer) < 8) {
+ dputs("disk size command return less than 8 bytes");
+ return false;
+ }
+
+ dcb->DCB_actual_blk_size = SWAPNC_32(*(uint32_t*)&buffer[4]);
+ dcb->DCB_actual_sector_cnt = SWAPNC_32(*(uint32_t*)&buffer[0]);
+
+ dprintf("%s: sectors %lu bytesPerSector %lu\n", __func__, (uint32_t)dcb->DCB_actual_sector_cnt, dcb->DCB_actual_blk_size);
+
+ // Ok, CHS time
+ guess_geometry( dcb->DCB_actual_sector_cnt, &dcb->DCB_actual_cyl_cnt, &dcb->DCB_actual_head_cnt, &dcb->DCB_actual_spt);
+ dcb->DCB_cmn.DCB_TSD_Flags |= DCB_TSD_ACTUAL_PRE_SET;
+
+ // Initilize apparent geometry from actual geometry, even though I'm not sure it is required
+ dcb->DCB_bdd.DCB_apparent_blk_size = dcb->DCB_actual_blk_size;
+ dcb->DCB_cmn.DCB_apparent_blk_shift = ulog2(dcb->DCB_bdd.DCB_apparent_blk_size);
+ dcb->DCB_bdd.DCB_apparent_sector_cnt = dcb->DCB_actual_sector_cnt;
+ dcb->DCB_bdd.DCB_apparent_cyl_cnt = dcb->DCB_actual_cyl_cnt;
+ dcb->DCB_bdd.DCB_apparent_head_cnt = dcb->DCB_actual_head_cnt;
+ dcb->DCB_bdd.DCB_apparent_spt = dcb->DCB_actual_spt;
+ dcb->DCB_cmn.DCB_TSD_Flags |= DCB_TSD_APPARENT_PRE_SET;
+
+ // max 255 blocks at a time
+ dcb->DCB_max_xfer_len = 255 * dcb->DCB_actual_blk_size;
+
+ if (ddb->sector_size != dcb->DCB_actual_blk_size) {
+ ddb->sector_size = dcb->DCB_actual_blk_size;
+ // Configure device for the new sector size
+ ch375_write_reg(&ddb->dev, CH375_REG_DISK_PKT_P_SEC, ddb->sector_size / CH375_BLOCK_SIZE);
+ }
+
+ return true;
+}
+
+/** Enables CH375 USB host mode, initializes "disk" mode on it
+ * and connects to a disk. */
+static bool disk_connect(CHUDISK_DDB *ddb)
+{
+ int tries;
+
+ dputs("switch to usb host");
+ ch375_set_usb_mode(&ddb->dev, CH375_USB_MODE_HOST_AUTO_SOF);
+ if (wait_for_ch375_result(&ddb->dev, 100) != CH375_INT_USB_CONNECT) {
+ dputs("could not turn on usb host");
+ return false;
+ }
+
+ dputs("try init disk");
+ ch375_disk_init(&ddb->dev);
+ if (wait_for_ch375_result(&ddb->dev, 1000) != 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(&ddb->dev);
+ if (wait_for_ch375_result(&ddb->dev, 1000) == CH375_INT_USB_SUCCESS) {
+ dputs("disk now init");
+ return true;
+ }
+ // give ample time (100ms) for drive to settle between tries
+ driver_ilb.ILB_Wait_10th_Sec();
+ }
+
+ dputs("disk failed to init");
+ return false;
+}
+
+/** Check if media has changed since last time.
+ * In addition, it will also try to connect to the disk.
+ * @return true if media changed. */
+static bool disk_update_media_state(CHUDISK_DDB *ddb, DCB *dcb)
+{
+ bool changed = false;
+ switch (ddb->state) {
+ case STATE_IDLE:
+ dprintf("%s: state idle -> changed=false\n", __func__);
+ break;
+ case STATE_CONNECTED:
+ // Recheck if disk is still ready
+ ch375_test_disk_ready(&ddb->dev);
+ if (wait_for_ch375_result(&ddb->dev, 100) == CH375_INT_USB_SUCCESS) {
+ // CH375 is ready
+ dprintf("%s: state connected -> disk ready -> changed=false\n", __func__);
+ } else {
+ // CH375 is not ready but we thought it was. USB has been removed!
+ dprintf("%s: state connected -> disk NOT ready -> changed=true\n", __func__);
+ changed = true;
+ ddb->state = STATE_IDLE;
+ }
+ break;
+ default:
+ dprintf("%s: busy state %u\n", __func__, ddb->state);
+ return false;
+ }
+ if (ddb->state == STATE_IDLE) {
+ // Geometry becomes invalid
+ dcb->DCB_cmn.DCB_TSD_Flags &= ~(DCB_TSD_APPARENT_PRE_SET | DCB_TSD_ACTUAL_PRE_SET);
+ // Try to connect to the disk
+ dprintf("%s: trying to connect\n", __func__);
+ if (disk_connect(ddb)) {
+ dprintf("%s: connected\n", __func__);
+ ddb->state = STATE_CONNECTED;
+ changed = true;
+ }
+ }
+ if (changed) {
+ dcb->DCB_cmn.DCB_device_flags |= DCB_DEV_UNCERTAIN_MEDIA;
+ if (ddb->state == STATE_IDLE) {
+ // if we can't find new media... for sure it has changed.
+ dcb->DCB_cmn.DCB_device_flags |= DCB_DEV_MEDIA_CHANGED;
+ }
+ } else {
+ dcb->DCB_cmn.DCB_device_flags &= ~(DCB_DEV_MEDIA_CHANGED | DCB_DEV_UNCERTAIN_MEDIA);
+ }
+ return changed;
+}
+
+/** IO request to recompute geometry.
+ * Normally called after a media change. */
+static bool ior_compute_geom(CHUDISK_DDB *ddb, IOP *iop)
+{
+ DCB *dcb = iop->IOP_physical_dcb;
+
+ dprintf("%s\n", __func__);
+
+ dcb_dump(dcb);
+
+ if (disk_update_media_state(ddb, dcb)) {
+ // report media change
+ dprintf("%s: uncertain media\n", __func__);
+ iop->IOP_ior.IOR_status = IORS_UNCERTAIN_MEDIA;
+ return true;
+ }
+ if (ddb->state == STATE_IDLE) {
+ dprintf("%s: no media\n", __func__);
+ iop->IOP_ior.IOR_status = IORS_NO_MEDIA;
+ return true;
+ }
+
+ if (disk_refresh_geometry(ddb, dcb)) {
+ dprintf("%s: success\n", __func__);
+ iop->IOP_ior.IOR_status = IORS_SUCCESS;
+ } else {
+ dprintf("%s: media error\n", __func__);
+ iop->IOP_ior.IOR_status = IORS_MEDIA_ERROR;
+ }
+
+ return true;
+}
+
+/** Checks if media has been changed since last call.
+ * Should return either UNCERTAIN_MEDIA or SUCCESS */
+static bool ior_media_check_reset(CHUDISK_DDB *ddb, IOP *iop)
+{
+ DCB *dcb = iop->IOP_physical_dcb;
+
+ dprintf("%s\n", __func__);
+ dcb_dump(dcb);
+
+ if (disk_update_media_state(ddb, dcb)) {
+ // report media change
+ dprintf("%s: uncertain media\n", __func__);
+ iop->IOP_ior.IOR_status = IORS_UNCERTAIN_MEDIA;
+ return true;
+ }
+
+ dprintf("%s: success\n", __func__);
+ iop->IOP_ior.IOR_status = IORS_SUCCESS;
+ return true;
+}
+
+/** Helper function to do one transfer (e.g. one SG_Buffer). */
+static uint32_t ior_xfer_do(CHUDISK_DDB *ddb, IOR *ior, uint8_t *buffer, uint32_t sector, uint32_t nsectors)
+{
+ if (ior->IOR_func == IOR_WRITE) {
+ return disk_write_sectors(ddb, buffer, sector, nsectors);
+ } else {
+ return disk_read_sectors(ddb, buffer, sector, nsectors);
+ }
+}
+
+/* Main entry point for I/O operation (read, write). */
+static bool ior_xfer(CHUDISK_DDB *ddb, IOP *iop)
+{
+ DCB *dcb = iop->IOP_physical_dcb;
+ IOR *ior = &iop->IOP_ior;
+ uint32_t sector = ior->IOR_start_addr;
+
+ dprintf("%s: %s start %u flags 0x%x\n", __func__,
+ ior->IOR_func == IOR_WRITE ? "write" : "read",
+ sector, ior->IOR_flags);
+ dcb_dump(dcb);
+
+ if (ior->IOR_flags & IORF_CHAR_COMMAND) {
+ // We do not support character operations. Only blocks.
+ ior->IOR_status = IORS_NOT_READY;
+ return true;
+ }
+
+ if (ior->IOR_flags & IORF_SCATTER_GATHER) {
+ _BlockDev_Scatter_Gather* sgd;
+ uint32_t nsectors;
+ for (sgd = ior->IOR_buffer_ptr; (nsectors = sgd->BD_SG_Count); sgd++) {
+ uint8_t *buffer = sgd->BD_SG_Buffer_Ptr;
+ dprintf("%s: sgd buffer %p sector %lu nsectors %lu\n", __func__, buffer, sector, nsectors);
+ if (ior_xfer_do(ddb, ior, buffer, sector, nsectors) != nsectors) {
+ ior->IOR_status = IORS_HW_FAILURE;
+ return true;
+ }
+ sector += nsectors;
+ }
+ } else {
+ uint8_t *buffer = ior->IOR_buffer_ptr;
+ uint32_t nsectors = ior->IOR_xfer_count;
+ dprintf("%s: plain buffer %p sector %lu nsectors %lu\n", __func__, buffer, sector, nsectors);
+ if (ior_xfer_do(ddb, ior, buffer, sector, nsectors) != nsectors) {
+ ior->IOR_status = IORS_HW_FAILURE;
+ return true;
+ }
+ }
+
+ ior->IOR_status = IORS_SUCCESS;
+ return true;
+}
+
+/* The I/O routine. This handles I/O request packets (IOPs), like READ or WRITE. */
+void ior(IOP *iop)
+{
+ CHUDISK_DDB *ddb = (CHUDISK_DDB*) iop->IOP_calldown_ptr->DCB_cd_ddb;
+ DCB *dcb = iop->IOP_physical_dcb;
+ bool handled = false;
+ dprintf("%s: ior_func=0x%x iop=%p ddb=%p flags=%x\n", __func__, iop->IOP_ior.IOR_func, iop, ddb, iop->IOP_ior.IOR_flags);
+
+ dcb->DCB_cmn.DCB_device_flags |= DCB_DEV_IO_ACTIVE;
+ switch (iop->IOP_ior.IOR_func) {
+ case IOR_COMPUTE_GEOM:
+ handled = ior_compute_geom(ddb, iop);
+ break;
+ case IOR_MEDIA_CHECK:
+ case IOR_MEDIA_CHECK_RESET:
+ handled = ior_media_check_reset(ddb, iop);
+ break;
+ case IOR_READ:
+ case IOR_WRITE:
+ handled = ior_xfer(ddb, iop);
+ break;
+ case IOR_FLUSH_DRIVE:
+ case IOR_FLUSH_DRIVE_AND_DISCARD:
+ handled = true; // Nothing to flush
+ break;
+ default:
+ dprintf("%s: unknown func=0x%x\n", __func__, iop->IOP_ior.IOR_func);
+ // Let everyone else have a go at this request
+ handled = false;
+ break;
+ }
+ dcb->DCB_cmn.DCB_device_flags &= ~DCB_DEV_IO_ACTIVE;
+ dprintf("%s: iop=%p %s status=%u\n", __func__, iop, handled ? "HANDLED" : "NOThandled", iop->IOP_ior.IOR_status);
+ if (handled) {
+ IopCallback(iop);
+ } else {
+ IopCalldown(iop);
+ }
+}
+
+/** The initialization routine.
+ * It should find out all information about the adapter we are supposed to be driving
+ * from CONFIGMG and the PnP device node, initialize the adapter in question,
+ * and write everything that we need in a new DDB which will be passed to us
+ * in future calls. */
+static AEP_result aer_initialize(AEP_bi_init *aep)
+{
+ DEVNODE devNode = (DEVNODE) aep->AEP_bi_i_hdevnode;
+ CMCONFIG config;
+ CONFIGRET cr;
+ CHUDISK_DDB *ddb;
+ struct ch375 dev;
+
+ dprintf("%s: devnode %p regkey %p\n", __func__, devNode, aep->AEP_bi_i_regkey);
+ // TODO Wish I could print the path of the regkey here.
+
+ // Get whichever configuration (IO ports, IRQ, etc.) has been allocated to our device
+ cr = CONFIGMG_Get_Alloc_Log_Conf(&config, devNode, CM_GET_ALLOC_LOG_CONF_ALLOC);
+ if (cr != CR_SUCCESS) {
+ dprintf("%s: get alloc log conf failure %u\n", __func__, cr);
+ return AEP_FAILURE;
+ }
+
+ dprintf("%s: config nports %u port len %u port base 0x%x\n", __func__,
+ config.num_io_ports, config.io_port_length[0], config.io_port_base[0]);
+
+ if (config.num_io_ports != 1 || config.io_port_length[0] != 2) {
+ // incorrect configuration
+ return AEP_FAILURE;
+ }
+
+ // Let's check if hardware exists at this address
+ dev.io_base = config.io_port_base[0];
+ dprintf("%s: io_base=%x reset...\n", __func__, dev.io_base);
+ ch375_reset(&dev);
+ driver_ilb.ILB_Wait_10th_Sec(); // Wait 100ms
+
+ if (ch375_check_exists(&dev, 0xA5) != (uint8_t)~0xA5) {
+ dprintf("%s: CH375 not detected at @%x\n", __func__, dev.io_base);
+ return AEP_FAILURE;
+ }
+
+ // Store it into the DDB, this also tells IOS we found one adapter.
+ ddb = (CHUDISK_DDB*) IspCreateDdb(&driver_ilb, sizeof(CHUDISK_DDB));
+ dprintf("%s: created 1 ddb at %p\n", __func__, ddb);
+ if (!ddb) {
+ return AEP_FAILURE;
+ }
+
+ // Fill in fields of our DDB
+ ddb->ddb.DDB_number_buses = 1;
+ ddb->dev.io_base = dev.io_base;
+ ddb->state = STATE_IDLE;
+ ddb->timeout = 0;
+ ddb->sector_size = 512;
+
+ // IOS will call device inquiry for each DDB created here.
+
+ // No SCSI...
+ aep->AEP_bi_i_max_lun = 0;
+ aep->AEP_bi_i_max_target = 0;
+
+ return AEP_SUCCESS;
+}
+
+/** Boot complete.
+ * Return failure if we're managing no devices and want to be unloaded. */
+static AEP_result aer_boot_complete(AEP_boot_done *aep)
+{
+ (void)aep;
+ dlog_init();
+ dprintf("%s: managed_devices=%u\n", __func__, managed_devices);
+ return managed_devices > 0 ? AEP_SUCCESS : AEP_FAILURE;
+}
+
+/** Called to configure a specific unit of an adapter.
+ * Driver must fill in DCB fields and insert itself into callback chain. */
+static AEP_result aer_config_dcb(AEP_dcb_config *aep)
+{
+ DCB * dcb = (DCB*) aep->AEP_d_c_dcb;
+ dprintf("%s: ddb %p dcb %p flags=%x\n", __func__, aep->AEP_d_c_hdr.AEP_ddb, dcb, dcb->DCB_cmn.DCB_device_flags);
+
+ // We get DCBs for both physical and logical devices, but we only want to handle physical
+ if (dcb->DCB_cmn.DCB_device_flags & DCB_DEV_PHYSICAL) {
+ CHUDISK_DDB *ddb = (CHUDISK_DDB*) aep->AEP_d_c_hdr.AEP_ddb;
+
+ if (managed_devices >= 1) {
+ // Only support 1 adapter, 1 unit
+ return AEP_FAILURE;
+ }
+
+ // Fill in DCB fields now
+ dcb->DCB_actual_blk_size = 512; // will update later if necessary
+ dcb->DCB_actual_blk_size = 9; // ulog2(512)
+ dcb->DCB_max_xfer_len = 255 * 512; // 255 packets
+ dcb->DCB_max_sg_elements = 255;
+
+ dcb_dump(dcb);
+
+ // Initialize unit (if necessary)
+ disk_update_media_state(ddb, dcb);
+ disk_refresh_geometry(ddb, dcb);
+
+ // Insert ourselves into the callback chain
+ dprintf("%s: inserting our calldown %p at dcb %p lgn %u\n",
+ __func__, ior, dcb, aep->AEP_d_c_hdr.AEP_lgn);
+ if (IspInsertCalldown(&driver_ilb, dcb, ior, (DDB*) ddb, 0,
+ DCB_dmd_serialize /* one request at a time */
+ | DCB_dmd_clear_media_chg | DCB_dmd_no_xclusive,
+ aep->AEP_d_c_hdr.AEP_lgn) != 0) {
+ dprintf("%s: isp insert calldown failure\n", __func__);
+ return AEP_FAILURE;
+ }
+
+ managed_devices++;
+ }
+
+ return AEP_SUCCESS;
+}
+
+/** Called to UNconfigure a specific adapter. */
+static AEP_result aer_unconfig_dcb(AEP_dcb_unconfig *aep)
+{
+ DCB * dcb = (DCB*) aep->AEP_d_u_dcb;
+ dprintf("%s: ddb %p dcb %p flags=%x\n", __func__, aep->AEP_d_u_hdr.AEP_ddb, dcb, dcb->DCB_cmn.DCB_device_flags);
+
+ if ((dcb->DCB_cmn.DCB_device_flags & DCB_DEV_PHYSICAL)
+ && memcmp(dcb->DCB_port_name, vxd_ddb.name, sizeof(dcb->DCB_port_name)) == 0) {
+ dprintf("%s: got our phys dcb\n", __func__);
+
+ managed_devices--;
+ }
+
+ return AEP_SUCCESS;
+}
+
+/** Called to warn us that a DCB is about to be unconfigured,
+ * and we should block until we finish all I/O to it. */
+static AEP_result aer_pend_unconfig_dcb(AEP_dcb_unconfig_pend *aep)
+{
+ DCB * dcb = (DCB*) aep->AEP_d_u_p_dcb;
+ dprintf("%s: ddb %p dcb %p flags=%x\n", __func__, aep->AEP_d_u_p_hdr.AEP_ddb, dcb, dcb->DCB_cmn.DCB_device_flags);
+
+ if ((dcb->DCB_cmn.DCB_device_flags & DCB_DEV_PHYSICAL)
+ && memcmp(dcb->DCB_port_name, vxd_ddb.name, sizeof(dcb->DCB_port_name)) == 0) {
+ dprintf("%s: got our phys dcb\n", __func__);
+ dcb_dump(dcb);
+
+ // We do not really have to do anything to finish all I/O
+ }
+
+ return AEP_SUCCESS;
+}
+
+
+/** Called multiple times, once for each potential unit supported by each adapter.
+ * Must fill in DCB if the unit exists. */
+static AEP_result aer_device_inquiry(AEP_inquiry_device *aep)
+{
+ PDCB dcb = (PDCB) aep->AEP_i_d_dcb;
+ dprintf("%s: ddb %p dcb %p unit %u\n", __func__, aep->AEP_i_d_hdr.AEP_ddb, dcb, dcb->DCB_unit_on_ctl);
+ if (dcb->DCB_unit_on_ctl > 0) {
+ return AEP_NO_MORE_DEVICES;
+ }
+
+ // No need to fill in all DCB fields, just cosmetic ones
+ dcb->DCB_bdd.DCB_BDD_flags |= BDF_Writeable | BDF_Removable | BDF_Serial_Cmd;
+ dcb->DCB_cmn.DCB_device_flags |= DCB_DEV_REMOVABLE | DCB_DEV_WRITEABLE | DCB_DEV_SERIAL_CMD | DCB_DEV_SYNC_MED_CHG_SUPPORT;
+
+ dcb->DCB_cmn.DCB_device_type = DCB_type_disk;
+ dcb->DCB_bus_type = DCB_BUS_ESDI;
+
+ memcpy(dcb->DCB_vendor_id, "CH375 ", sizeof(dcb->DCB_vendor_id));
+ memcpy(dcb->DCB_product_id, "USB Disk ", sizeof(dcb->DCB_product_id));
+ memcpy(dcb->DCB_rev_level, "0001", sizeof(dcb->DCB_rev_level));
+
+ // This we use to identify our DCB later on
+ memcpy(dcb->DCB_port_name, vxd_ddb.name, sizeof(dcb->DCB_port_name));
+
+ return AEP_SUCCESS;
+}
+
+/** Called before removing our driver from memory. */
+static AEP_result aer_uninitialize(AEP_bi_uninit *aep)
+{
+ (void) aep;
+ dputs(__func__);
+ return AEP_SUCCESS;
+}
+
+/** A inflight IO request has timed out. Cancel it and reset device. */
+static AEP_result aer_timeout(AEP_iop_timeout_occurred *aep)
+{
+ IOP* iop = aep->AEP_i_t_o_iop;
+ dprintf("%s: timeout occured for IOP %p\n", __func__, iop);
+ // We have our own timeouts in each poll function
+ // So we can ignore the timeout here.
+ // Re-arm the timer.
+ iop->IOP_timer = iop->IOP_timer_orig;
+ // If we ever move to interrupts, we will have to do something here.
+ return AEP_SUCCESS;
+}
+
+/** The "Asynchronous Event Routine" (i.e. IOS main entry point.) */
+void aer(AEP *aep)
+{
+ switch (aep->AEP_func) {
+ case AEP_INITIALIZE:
+ aep->AEP_result = aer_initialize(&aep->bi_i);
+ break;
+ case AEP_BOOT_COMPLETE:
+ aep->AEP_result = aer_boot_complete(&aep->b_d);
+ break;
+ case AEP_CONFIG_DCB:
+ aep->AEP_result = aer_config_dcb(&aep->d_c);
+ break;
+ case AEP_UNCONFIG_DCB:
+ aep->AEP_result = aer_unconfig_dcb(&aep->d_u);
+ break;
+ case AEP_DEVICE_INQUIRY:
+ aep->AEP_result = aer_device_inquiry(&aep->i_d);
+ break;
+ case AEP_UNINITIALIZE:
+ aep->AEP_result = aer_uninitialize(&aep->bi_u);
+ break;
+ case AEP_IOP_TIMEOUT:
+ aep->AEP_result = aer_timeout(&aep->i_t_o);
+ break;
+ case AEP_PEND_UNCONFIG_DCB:
+ aep->AEP_result = aer_pend_unconfig_dcb(&aep->d_u_p);
+ break;
+ default:
+ dprintf("%s unknown func=%u\n", __func__, aep->AEP_func);
+ aep->AEP_result = AEP_FAILURE;
+ break;
+ }
+}
+
+/** The VxD main entry point. */
+int control(uint32_t message, uint32_t ebx, uint32_t ecx, uint32_t edx, uint32_t esi, uint32_t edi)
+{
+ int ret = 0;
+ (void) ebx;
+ (void) ecx;
+ (void) edx;
+ (void) esi;
+ (void) edi;
+
+ switch (message) {
+ case SYS_DYNAMIC_DEVICE_INIT:
+ dlog_init();
+ ret = IOS_Get_Version();
+ dprintf("ios get version = %x\n", ret);
+ IOS_Register(&driver_drp);
+ dprintf("ios registration result=%u\n", driver_drp.DRP_reg_result);
+ switch (driver_drp.DRP_reg_result) {
+ case DRP_REMAIN_RESIDENT:
+ case DRP_MINIMIZE:
+ break;
+ default: // For anything else, signal an error so that we are unloaded
+ set_carry();
+ return 0;
+
+ }
+ break;
+ }
+
+ clear_carry();
+ return 0;
+}
diff --git a/w9x/dumpdcb.h b/w9x/dumpdcb.h
new file mode 100644
index 0000000..74679c0
--- /dev/null
+++ b/w9x/dumpdcb.h
@@ -0,0 +1,110 @@
+/* Debugging routines to dump the DCB structure and flags to dlog. */
+
+#ifndef DUMPDCB_H
+#define DUMPDCB_H
+
+static void print_dev_flags(uint32_t bitset) {
+ if (bitset & DCB_DEV_SPINDOWN_SUPPORTED) dprintf(" DCB_DEV_SPINDOWN_SUPPORTED");
+ if (bitset & DCB_DEV_SPUN_DOWN) dprintf(" DCB_DEV_SPUN_DOWN");
+ if (bitset & DCB_DEV_IO_ACTIVE) dprintf(" DCB_DEV_IO_ACTIVE");
+ if (bitset & DCB_DEV_INT13_CONFIGURED) dprintf(" DCB_DEV_INT13_CONFIGURED");
+ if (bitset & DCB_DEV_NO_ASPICAM) dprintf(" DCB_DEV_NO_ASPICAM");
+ if (bitset & DCB_DEV_REMOVAL_PENDING) dprintf(" DCB_DEV_REMOVAL_PENDING");
+ if (bitset & DCB_DEV_RMM_INT13) dprintf(" DCB_DEV_RMM_INT13");
+ if (bitset & DCB_DEV_TSD_PROCESSED) dprintf(" DCB_DEV_TSD_PROCESSED");
+ if (bitset & DCB_DEV_SINGLE_FLOPPY) dprintf(" DCB_DEV_SINGLE_FLOPPY");
+ if (bitset & DCB_DEV_REAL_MODE) dprintf(" DCB_DEV_REAL_MODE");
+ if (bitset & DCB_DEV_QUE_DESCEND) dprintf(" DCB_DEV_QUE_DESCEND");
+ if (bitset & DCB_DEV_MEDIA_CHANGED) dprintf(" DCB_DEV_MEDIA_CHANGED");
+ if (bitset & DCB_DEV_INVALID_GEOM) dprintf(" DCB_DEV_INVALID_GEOM");
+ if (bitset & DCB_DEV_INHIBIT_SW_TIMED_SUPPORT) dprintf(" DCB_DEV_INHIBIT_SW_TIMED_SUPPORT");
+ if (bitset & DCB_DEV_ASYNC_MED_CHG_SUPPORT) dprintf(" DCB_DEV_ASYNC_MED_CHG_SUPPORT");
+ if (bitset & DCB_DEV_SYNC_MED_CHG_SUPPORT) dprintf(" DCB_DEV_SYNC_MED_CHG_SUPPORT");
+ if (bitset & DCB_DEV_PHYSICAL) dprintf(" DCB_DEV_PHYSICAL");
+ if (bitset & DCB_DEV_LOGICAL) dprintf(" DCB_DEV_LOGICAL");
+ if (bitset & DCB_DEV_RMM) dprintf(" DCB_DEV_RMM");
+ if (bitset & DCB_DEV_UNCERTAIN_MEDIA) dprintf(" DCB_DEV_UNCERTAIN_MEDIA");
+ if (bitset & DCB_DEV_ROM_CFG_REQ) dprintf(" DCB_DEV_ROM_CFG_REQ");
+ if (bitset & DCB_DEV_MUST_CONFIGURE) dprintf(" DCB_DEV_MUST_CONFIGURE");
+ if (bitset & DCB_DEV_SSTOR_VOL) dprintf(" DCB_DEV_SSTOR_VOL");
+ if (bitset & DCB_DEV_PAGING_DEVICE) dprintf(" DCB_DEV_PAGING_DEVICE");
+ if (bitset & DCB_CHAR_DEVICE) dprintf(" DCB_CHAR_DEVICE");
+ if (bitset & DCB_DEV_I13_IN_PROGRESS) dprintf(" DCB_DEV_I13_IN_PROGRESS");
+ if (bitset & DCB_DEV_CACHE) dprintf(" DCB_DEV_CACHE");
+ if (bitset & DCB_DEV_SERIAL_CMD) dprintf(" DCB_DEV_SERIAL_CMD");
+ if (bitset & DCB_DEV_REMOTE) dprintf(" DCB_DEV_REMOTE");
+ if (bitset & DCB_DEV_REMOVABLE) dprintf(" DCB_DEV_REMOVABLE");
+ if (bitset & DCB_DEV_WRITEABLE) dprintf(" DCB_DEV_WRITEABLE");
+ if (bitset & DCB_DEV_INT13_DRIVE) dprintf(" DCB_DEV_INT13_DRIVE");
+}
+
+void print_tsd_flags(uint32_t flags) {
+ if (flags & DCB_TSD_INVALID_PARTITION) dprintf(" DCB_TSD_INVALID_PARTITION");
+ if (flags & DCB_TSD_BAD_MBR) dprintf(" DCB_TSD_BAD_MBR");
+ if (flags & DCB_TSD_USER_LETTERS_CHECKED) dprintf(" DCB_TSD_USER_LETTERS_CHECKED");
+ if (flags & DCB_TSD_FIRST_USER_CHECK) dprintf(" DCB_TSD_FIRST_USER_CHECK");
+ if (flags & DCB_TSD_USER_SET) dprintf(" DCB_TSD_USER_SET");
+ if (flags & DCB_TSD_NO_USER_INT13) dprintf(" DCB_TSD_NO_USER_INT13");
+ if (flags & DCB_TSD_PROTMODE_INT_13) dprintf(" DCB_TSD_PROTMODE_INT_13");
+ if (flags & DCB_TSD_NONEXIST_PARTITION) dprintf(" DCB_TSD_NONEXIST_PARTITION");
+ if (flags & DCB_TSD_MBPB_PBR) dprintf(" DCB_TSD_MBPB_PBR");
+ if (flags & DCB_TSD_RBPB_INIT) dprintf(" DCB_TSD_RBPB_INIT");
+ if (flags & DCB_TSD_MBPB_INIT) dprintf(" DCB_TSD_MBPB_INIT");
+ if (flags & DCB_TSD_APPARENT_PBR) dprintf(" DCB_TSD_APPARENT_PBR");
+ if (flags & DCB_TSD_APPARENT_ROM) dprintf(" DCB_TSD_APPARENT_ROM");
+ if (flags & DCB_TSD_ACTUAL_SET) dprintf(" DCB_TSD_ACTUAL_SET");
+ if (flags & DCB_TSD_ACTUAL_PRE_SET) dprintf(" DCB_TSD_ACTUAL_PRE_SET");
+ if (flags & DCB_TSD_APPARENT_PRE_SET) dprintf(" DCB_TSD_APPARENT_PRE_SET");
+}
+
+void print_dmd_flags(uint32_t bitset) {
+ if (bitset & DCB_dmd_srb_cdb) dprintf(" %s", "DCB_dmd_srb_cdb");
+ if (bitset & DCB_dmd_logical) dprintf(" %s", "DCB_dmd_logical");
+ if (bitset & DCB_dmd_physical) dprintf(" %s", "DCB_dmd_physical");
+ if (bitset & DCB_dmd_small_memory) dprintf(" %s", "DCB_dmd_small_memory");
+ if (bitset & DCB_dmd_not_512) dprintf(" %s", "DCB_dmd_not_512");
+ if (bitset & DCB_dmd_word_align) dprintf(" %s", "DCB_dmd_word_align");
+ if (bitset & DCB_dmd_dword_align) dprintf(" %s", "DCB_dmd_dword_align");
+ if (bitset & DCB_dmd_phys_sgd) dprintf(" %s", "DCB_dmd_phys_sgd");
+ if (bitset & DCB_dmd_phys_sgd_ptr) dprintf(" %s", "DCB_dmd_phys_sgd_ptr");
+ if (bitset & DCB_dmd_do_a_b_toggling) dprintf(" %s", "DCB_dmd_do_a_b_toggling");
+ if (bitset & DCB_dmd_query_remov) dprintf(" %s", "DCB_dmd_query_remov");
+ if (bitset & DCB_dmd_request_sns) dprintf(" %s", "DCB_dmd_request_sns");
+ if (bitset & DCB_dmd_lock_unlock_media) dprintf(" %s", "DCB_dmd_lock_unlock_media");
+ if (bitset & DCB_dmd_load_eject_media) dprintf(" %s", "DCB_dmd_load_eject_media");
+ if (bitset & DCB_dmd_clear_media_chg) dprintf(" %s", "DCB_dmd_clear_media_chg");
+ if (bitset & DCB_dmd_prot_mode) dprintf(" %s", "DCB_dmd_prot_mode");
+ if (bitset & DCB_dmd_no_xclusive) dprintf(" %s", "DCB_dmd_no_xclusive");
+ if (bitset & DCB_dmd_pageability) dprintf(" %s", "DCB_dmd_pageability");
+}
+
+
+static void dcb_dump(const DCB *dcb)
+{
+#if ENABLE_DLOG
+ if (dcb->DCB_cmn.DCB_device_flags & DCB_DEV_PHYSICAL) {
+ dprintf("DCB %p physical prod %.8s %.16s %.4s port %.8s\n", dcb, dcb->DCB_vendor_id, dcb->DCB_product_id, dcb->DCB_rev_level, dcb->DCB_port_name);
+ dprintf(" geom apparent blksize=%u(%u) sectors=%u c=%u h=%u s=%u\n",
+ dcb->DCB_bdd.DCB_apparent_blk_size, dcb->DCB_cmn.DCB_apparent_blk_shift, (uint32_t) dcb->DCB_bdd.DCB_apparent_sector_cnt,
+ dcb->DCB_bdd.DCB_apparent_cyl_cnt, dcb->DCB_bdd.DCB_apparent_head_cnt, dcb->DCB_bdd.DCB_apparent_spt);
+ dprintf(" geom actual blksize=%u sectors=%u c=%u h=%u s=%u\n",
+ dcb->DCB_actual_blk_size, (uint32_t) dcb->DCB_actual_sector_cnt,
+ dcb->DCB_actual_cyl_cnt, dcb->DCB_actual_head_cnt, dcb->DCB_actual_spt);
+ dprintf(" max xfer=%u sg elems=%u\n", dcb->DCB_max_xfer_len, dcb->DCB_max_sg_elements);
+ } else {
+ dprintf("DCB %p logical\n", dcb);
+ }
+ // only dump DCB_cmn fields here, since they are common to both logical/physical
+ dprintf(" dev flags %x :", dcb->DCB_cmn.DCB_device_flags);
+ print_dev_flags(dcb->DCB_cmn.DCB_device_flags);
+ dputc('\n');
+ dprintf(" TSD flags %x :", dcb->DCB_cmn.DCB_TSD_Flags);
+ print_tsd_flags(dcb->DCB_cmn.DCB_TSD_Flags);
+ dputc('\n');
+ dprintf(" DMD flags %x :", dcb->DCB_cmn.DCB_dmd_flags);
+ print_dmd_flags(dcb->DCB_cmn.DCB_dmd_flags);
+ dputc('\n');
+#endif
+}
+
+#endif // DUMPDCB_H
diff --git a/w9x/inc/configmg.h b/w9x/inc/configmg.h
new file mode 100644
index 0000000..d3cec38
--- /dev/null
+++ b/w9x/inc/configmg.h
@@ -0,0 +1,213 @@
+#ifndef CONFIGMG_H
+#define CONFIGMG_H
+
+/** CONFIGMG VxD services and types.
+ * Manually generated for now. */
+
+enum CONFIGMG_RESULT {
+ CR_SUCCESS = 0x0,
+ CR_DEFAULT = 0x1,
+ CR_OUT_OF_MEMORY = 0x2,
+ CR_INVALID_POINTER = 0x3,
+ CR_INVALID_FLAG = 0x4,
+ CR_INVALID_DEVNODE = 0x5,
+ CR_INVALID_RES_DES = 0x6,
+ CR_INVALID_LOG_CONF = 0x7,
+ CR_INVALID_ARBITRATOR = 0x8,
+ CR_INVALID_NODELIST = 0x9,
+ CR_DEVNODE_HAS_REQS = 0xA,
+ CR_INVALID_RESOURCEID = 0xB,
+ CR_DLVXD_NOT_FOUND = 0xC,
+ CR_NO_SUCH_DEVNODE = 0xD,
+ CR_NO_MORE_LOG_CONF = 0xE,
+ CR_ALREADY_SUCH_DEVNODE = 0x10,
+ CR_INVALID_RANGE_LIST = 0x11,
+ CR_INVALID_RANGE = 0x12,
+ CR_FAILURE = 0x13,
+ CR_NO_SUCH_LOGICAL_KEY = 0x14,
+ CR_CREATE_BLOCKED = 0x15,
+ CR_NOT_SYSTEM_VM = 0x16,
+ CR_REMOVE_VETOED = 0x17,
+ CR_APM_VETOED = 0x18,
+ CR_INVALID_LOAD_TYPE = 0x19,
+ CR_BUFFER_SMALL = 0x1A,
+ // ...
+};
+typedef uint32_t CONFIGRET;
+
+
+enum DLVXD_LOAD_TYPE {
+ DLVXD_LOAD_ENUMERATE = 0,
+ DLVXD_LOAD_DEVLOADER = 1,
+ DLVXD_LOAD_DRIVER = 2
+};
+
+// Handles
+/** Device node */
+typedef uint32_t DEVNODE;
+/** Logical configuration */
+typedef uint32_t LOG_CONF, *PLOG_CONF;
+
+#define MAX_MEM_REGISTERS 9
+#define MAX_IO_PORTS 20
+#define MAX_IRQS 7
+#define MAX_DMA_CHANNELS 7
+
+typedef _Packed struct cm_config {
+ uint16_t num_mem_windows;
+ uint32_t mem_base[MAX_MEM_REGISTERS];
+ uint32_t mem_length[MAX_MEM_REGISTERS];
+ uint16_t mem_attrib[MAX_MEM_REGISTERS];
+ uint16_t num_io_ports;
+ uint16_t io_port_base[MAX_IO_PORTS];
+ uint16_t io_port_length[MAX_IO_PORTS];
+ uint16_t num_irqs;
+ uint8_t irq_registers[MAX_IRQS];
+ uint8_t irq_attrib[MAX_IRQS];
+ uint16_t num_dmas;
+ uint8_t dma_lst[MAX_DMA_CHANNELS];
+ uint16_t dma_attrib[MAX_DMA_CHANNELS];
+ uint8_t reserved[3];
+} CMCONFIG;
+
+static inline CONFIGRET CONFIGMG_Get_Parent(DEVNODE *out, DEVNODE node, unsigned long flags);
+#pragma aux CONFIGMG_Get_Parent = \
+ "int 0x20" \
+ "dd ( 0x33 shl 16 ) + 3" \
+ __parm __caller [] __value [eax] __modify [eax ecx edx]
+
+static inline CONFIGRET CONFIGMG_Get_Child(DEVNODE *out, DEVNODE node, unsigned long flags);
+#pragma aux CONFIGMG_Get_Child = \
+ "int 0x20" \
+ "dd ( 0x33 shl 16 ) + 4" \
+ __parm __caller [] __value [eax] __modify [eax ecx edx]
+
+static inline CONFIGRET CONFIGMG_Get_Private_DWord(unsigned long *value, DEVNODE inDevNode, DEVNODE forDevNode, unsigned long flags);
+#pragma aux CONFIGMG_Get_Private_DWord = \
+ "int 0x20" \
+ "dd ( 0x33 shl 16 ) + 9" \
+ __parm __caller [] __value [eax] __modify [eax ecx edx]
+
+static inline CONFIGRET CONFIGMG_Set_Private_DWord(DEVNODE inDevNode, DEVNODE forDevNode, unsigned long value, unsigned long flags);
+#pragma aux CONFIGMG_Set_Private_DWord = \
+ "int 0x20" \
+ "dd ( 0x33 shl 16 ) + 10" \
+ __parm __caller [] __value [eax] __modify [eax ecx edx]
+
+static inline CONFIGRET CONFIGMG_Create_DevNode(DEVNODE *out, const char * deviceId, DEVNODE parent, unsigned long flags);
+#pragma aux CONFIGMG_Create_DevNode = \
+ "int 0x20" \
+ "dd ( 0x33 shl 16 ) + 11" \
+ __parm __caller [] __value [eax] __modify [eax ecx edx]
+
+static inline CONFIGRET CONFIGMG_Set_Bus_Info(DEVNODE devnode, uint32_t busType, unsigned long infoSize, void *info, unsigned long flags);
+#pragma aux CONFIGMG_Set_Bus_Info = \
+ "int 0x20" \
+ "dd ( 0x33 shl 16 ) + 67" \
+ __parm __caller [] __value [eax] __modify [eax ecx edx]
+
+static inline CONFIGRET CONFIGMG_Get_Bus_Info(DEVNODE devnode, uint32_t busType, unsigned long *infoSize, void *info, unsigned long flags);
+#pragma aux CONFIGMG_Set_Bus_Info = \
+ "int 0x20" \
+ "dd ( 0x33 shl 16 ) + 67" \
+ __parm __caller [] __value [eax] __modify [eax ecx edx]
+
+enum CONFIGFUNCS {
+ /** Check if the given resources are acceptable. */
+ CONFIG_FILTER = 0x0,
+ /** Start using hardware with the given resources. */
+ CONFIG_START = 0x1,
+ /** Stop using the allocated resources. */
+ CONFIG_STOP = 0x2,
+ CONFIG_TEST = 0x3,
+ /** Send to devnode and parents when removed from the tree.
+ * Stop using the hardware and deallocate all instance data. */
+ CONFIG_REMOVE = 0x4,
+ CONFIG_ENUMERATE = 0x5,
+ CONFIG_SETUP = 0x6,
+ CONFIG_CALLBACK = 0x7,
+ CONFIG_APM = 0x8,
+ CONFIG_TEST_FAILED = 0x9,
+ CONFIG_TEST_SUCCEEDED = 0xA,
+ CONFIG_VERIFY_DEVICE = 0xB,
+ CONFIG_PREREMOVE = 0xC,
+ CONFIG_SHUTDOWN = 0xD,
+ CONFIG_PREREMOVE2 = 0xE,
+ CONFIG_READY = 0xF,
+ CONFIG_PROP_CHANGE = 0x10,
+ CONFIG_PRIVATE = 0x11,
+ CONFIG_PRESHUTDOWN = 0x12
+};
+
+enum SUBCONFIGFUNCS {
+ CONFIG_START_DYNAMIC_START = 0x0,
+ CONFIG_START_FIRST_START = 0x1,
+
+ CONFIG_STOP_DYNAMIC_STOP = 0x0,
+ CONFIG_STOP_HAS_PROBLEM = 0x1,
+
+ CONFIG_REMOVE_DYNAMIC = 0x0,
+ CONFIG_REMOVE_SHUTDOWN = 0x1,
+ CONFIG_REMOVE_REBOOT = 0x2
+};
+
+typedef uint32_t CONFIGFUNC;
+typedef uint32_t SUBCONFIGFUNC;
+typedef CONFIGRET (__cdecl *CMCONFIGHANDLER)(CONFIGFUNC func, SUBCONFIGFUNC subFunc, DEVNODE devNode, unsigned long refData, unsigned long flags);
+enum register_device_driver_flags {
+ CM_REGISTER_DEVICE_DRIVER_STATIC = 0,
+ CM_REGISTER_DEVICE_DRIVER_DISABLEABLE = 1,
+ CM_REGISTER_DEVICE_DRIVER_REMOVABLE = 2
+};
+
+static inline CONFIGRET CONFIGMG_Register_Device_Driver(DEVNODE devNode, CMCONFIGHANDLER handler, unsigned long refData, unsigned long flags);
+#pragma aux CONFIGMG_Register_Device_Driver = \
+ "int 0x20" \
+ "dd ( 0x33 shl 16 ) + 14" \
+ __parm __caller [] __value [eax] __modify [eax ecx edx]
+
+typedef CONFIGRET (__cdecl *CMENUMFUNCTION)(CONFIGFUNC func, SUBCONFIGFUNC subFunc, DEVNODE bus, DEVNODE about, unsigned long flags);
+enum register_enumerator_flags {
+ CM_REGISTER_ENUMERATOR_SOFTWARE = 0,
+ CM_REGISTER_ENUMERATOR_HARDWARE = 1
+};
+
+static inline CONFIGRET CONFIGMG_Register_Enumerator(DEVNODE devNode, CMENUMFUNCTION handler, unsigned long flags);
+#pragma aux CONFIGMG_Register_Enumerator = \
+ "int 0x20" \
+ "dd ( 0x33 shl 16 ) + 15" \
+ __parm __caller [] __value [eax] __modify [eax ecx edx]
+
+
+enum LOG_CONF_FLAS {
+ BASIC_LOG_CONG = 0,
+ FILTERED_LOG_CONF = 1,
+ ALLOC_LOG_CONF = 2,
+ BOOT_LOG_CONF = 3,
+ FORCED_LOG_CONF = 4
+};
+
+static inline CONFIGRET CONFIGMG_Get_First_Log_Conf(LOG_CONF *logconf, DEVNODE devNode, unsigned long flags);
+#pragma aux CONFIGMG_Get_First_Log_Conf = \
+ "int 0x20" \
+ "dd ( 0x33 shl 16 ) + 26" \
+ __parm __caller [] __value [eax] __modify [eax ecx edx]
+
+static inline CONFIGRET CONFIGMG_Get_Next_Log_Conf(LOG_CONF *logconf, DEVNODE prevLogConf, unsigned long flags);
+#pragma aux CONFIGMG_Get_Next_Log_Conf = \
+ "int 0x20" \
+ "dd ( 0x33 shl 16 ) + 27" \
+ __parm __caller [] __value [eax] __modify [eax ecx edx]
+
+enum CM_GET_ALLOC_LOG_CONF_FLAGS {
+ CM_GET_ALLOC_LOG_CONF_ALLOC = 0,
+ CM_GET_ALLOC_LOG_CONF_BOOT_ALLOC = 1
+};
+
+static inline CONFIGRET CONFIGMG_Get_Alloc_Log_Conf(CMCONFIG *config, DEVNODE devNode, unsigned long flags);
+#pragma aux CONFIGMG_Get_Alloc_Log_Conf = \
+ "int 0x20" \
+ "dd ( 0x33 shl 16 ) + 59" \
+ __parm __caller [] __value [eax] __modify [eax ecx edx]
+
+#endif // CONFIGMG_H
diff --git a/w9x/inc/ios.h b/w9x/inc/ios.h
new file mode 100644
index 0000000..59214d8
--- /dev/null
+++ b/w9x/inc/ios.h
@@ -0,0 +1,1520 @@
+#ifndef IOS_H
+#define IOS_H
+
+/** This is a big mess of IOS structs and functions.
+ * They come from the storage.doc / FAQ.
+ * There are also some VxDCall wrappers, manually generated.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include "sassert.h"
+
+typedef void __cdecl (*IO_callback)(struct IOP *iop);
+
+// IOS ================
+
+#define IOS_DEVICE_ID 0x10
+
+static inline uint32_t IOS_Get_Version(void);
+#pragma aux IOS_Get_Version = \
+ "int 0x20" \
+ "dd ( 0x10 shl 16 ) + 0" \
+ "jnc ok" \
+ "xor eax, eax" \
+ "ok:" \
+ __parm __caller [] __value [eax] __modify [ecx edx]
+
+static inline void IOS_Register(const struct DRP *drp);
+#pragma aux IOS_Register = \
+ "int 0x20" \
+ "dd ( 0x10 shl 16 ) + 7" \
+ __parm __caller [] __modify [eax ecx edx]
+
+// AEP ================
+
+typedef _Packed struct AEPHDR {
+ uint16_t AEP_func; // function; see below
+ uint16_t AEP_result; // result; set to zero if successful; see below
+ uint32_t AEP_ddb; // address of DDB
+ uint8_t AEP_lgn; // current load group number (from DVT)
+ uint8_t AEP_align[3]; // reserved; don't use
+} AEPHDR, *PAEPHDR;
+
+typedef _Packed struct AEP_bi_init {
+ AEPHDR AEP_bi_i_hdr;
+ uint32_t AEP_bi_reference_data; // optional value passed in from DRP
+ uint8_t AEP_bi_flags; // flags; see below
+ uint8_t AEP_bi_i_max_target; // max scsi target id
+ uint8_t AEP_bi_i_max_lun; // max scsi lun
+ uint32_t AEP_bi_i_dcb; // init DCB for port drivers
+ void* AEP_bi_i_hdevnode; // handle to devnode for this adapter
+ void* AEP_bi_i_regkey; // registry key for this adapter
+ uint8_t AEP_bi_i_align[1]; // reserved; don't use
+} AEP_bi_init, *PAEP_bi_init;
+
+typedef _Packed struct AEP_boot_done {
+ AEPHDR AEP_bi_i_hdr;
+} AEP_boot_done, *PAEP_boot_done;
+
+typedef _Packed struct AEP_dcb_config {
+ struct AEPHDR AEP_d_c_hdr;
+ struct DCB *AEP_d_c_dcb; // 32-bit ptr to DCB
+} AEP_dcb_config, *PAEP_dcb_config;
+
+typedef _Packed struct AEP_dcb_unconfig {
+ struct AEPHDR AEP_d_u_hdr;
+ struct DCB *AEP_d_u_dcb; // 32-bit ptr to DCB
+} AEP_dcb_unconfig, *PAEP_dcb_unconfig;
+
+typedef _Packed struct AEP_iop_timeout_occurred {
+ struct AEPHDR AEP_i_t_o_hdr;
+ struct IOP *AEP_i_t_o_iop; // address of IOP that timed out
+} AEP_iop_timeout_occurred, *PAEP_iop_timeout_occurred;
+
+typedef _Packed struct AEP_inquiry_device {
+ struct AEPHDR AEP_i_d_hdr;
+ uint32_t AEP_i_d_dcb; // address of DCB
+} AEP_inquiry_device, *PAEP_inquiry_device;
+
+typedef _Packed struct AEP_bi_uninit {
+ struct AEPHDR AEP_bi_u_hdr;
+} AEP_bi_uninit, *PAEP_bi_uninit;
+
+typedef struct AEP_dcb_unconfig_pend {
+ struct AEPHDR AEP_d_u_p_hdr;
+ struct DCB *AEP_d_u_p_dcb; // 32-bit ptr to DCB
+} AEP_dcb_unconfig_pend, *PAEP_dcb_unconfig_pend;
+
+typedef _Packed union AEP {
+ struct AEPHDR;
+ struct AEP_bi_init bi_i;
+ struct AEP_boot_done b_d;
+ struct AEP_dcb_config d_c;
+ struct AEP_dcb_unconfig d_u;
+ struct AEP_iop_timeout_occurred i_t_o;
+ struct AEP_inquiry_device i_d;
+ struct AEP_bi_uninit bi_u;
+ struct AEP_dcb_unconfig_pend d_u_p;
+} AEP, *PAEP;
+
+typedef enum AEP_result {
+ AEP_SUCCESS = 0,
+ AEP_FAILURE = -1,
+
+ AEP_NO_INQ_DATA = 1,
+ AEP_NO_MORE_DEVICES = 2
+} AEP_result;
+
+enum AEP_funcs {
+ /** initializes driver/interface */
+ AEP_INITIALIZE = 0,
+ /** indicates system critical shutdown */
+ AEP_SYSTEM_CRIT_SHUTDOWN = 1,
+ /** booting done - switch to run time */
+ AEP_BOOT_COMPLETE = 2,
+ /* configure device */
+ AEP_CONFIG_DCB = 3,
+ /* unconfigure device */
+ AEP_UNCONFIG_DCB = 4,
+ /* iop timeout occured */
+ AEP_IOP_TIMEOUT = 5,
+ /* get device indentification data */
+ AEP_DEVICE_INQUIRY = 6,
+ /* 1/2 second interval notification */
+ AEP_HALF_SEC = 7,
+ /* one second interval notification */
+ AEP_1_SEC = 8,
+ /* two second interval notification */
+ AEP_2_SECS = 9,
+ /* four second interval notification*/
+ AEP_4_SECS = 10,
+ /* parameters for '.I' debug command*/
+ AEP_DBG_DOT_CMD = 11,
+ /* associate logical DCBs w/ physical */
+ AEP_ASSOCIATE_DCB = 12,
+ /* IFSMGR about to hook in */
+ AEP_REAL_MODE_HANDOFF = 13,
+ /* indicates system shutdown */
+ AEP_SYSTEM_SHUTDOWN = 14,
+ /* uninitialize driver/interface */
+ AEP_UNINITIALIZE = 15,
+ /* logical dcb is going to be locked */
+ AEP_DCB_LOCK = 16,
+ /* notify drivers of volume mount */
+ AEP_MOUNT_NOTIFY = 17,
+ /* notify drivers of VRP creation */
+ AEP_CREATE_VRP = 18,
+ /* notify drivers of VRP destruction */
+ AEP_DESTROY_VRP = 19,
+ /* tell drivers to refresh drive data */
+ AEP_REFRESH_DRIVE = 20,
+ /** tell drivers that DCB removal is pending */
+ AEP_PEND_UNCONFIG_DCB = 21,
+ /** notify int13 of a possible change in the int 1eh vector for copy protection */
+ AEP_1E_VEC_UPDATE = 22,
+ /** inform driver vsd to change the floppy spindle speed */
+ AEP_CHANGE_RPM = 23,
+};
+
+// BlockDev ===========
+
+typedef struct BlockDev_Scatter_Gather{
+ uint32_t BD_SG_Count; // Block count; else byte count if IORF_CHAR_COMMAND set
+ void* BD_SG_Buffer_Ptr;
+} _BlockDev_Scatter_Gather;
+
+enum BDD_FLAGS {
+ BDF_Int13_Drive = 0x01,
+ BDF_Int13_Drive_Bit = 0,
+
+ BDF_Writeable = 0x02,
+ BDF_Writeable_Bit = 1,
+
+ BDF_Removable = 0x04,
+ BDF_Removable_Bit = 2,
+
+ BDF_Remote = 0x08,
+ BDF_Remote_Bit = 3,
+
+ BDF_Serial_Cmd = 0x10,
+ BDF_Serial_Cmd_Bit = 4,
+
+ BDF_Cache = 0x20,
+ BDF_Cache_Bit = 5,
+};
+
+// DCB ================
+
+typedef _Packed struct _DCB_COMMON {
+ uint32_t DCB_physical_dcb; // DCB for physical device
+ uint32_t DCB_expansion_length; // expansion length; see below
+ struct DCB_cd_entry *DCB_ptr_cd; // address of calldown list
+ uint32_t DCB_next_dcb; // link to next DCB
+ uint32_t DCB_next_logical_dcb; // address of next logical dcb
+ uint8_t DCB_drive_lttr_equiv; // drive number; see below
+ uint8_t DCB_unit_number; // drive or unit #; see below
+ uint16_t DCB_TSD_Flags; // TSD flags; see below
+
+ // Volume Tracking
+ uint32_t DCB_vrp_ptr; // address of VRP for this DCB
+ uint32_t DCB_dmd_flags; // demand bits; see below
+ uint32_t DCB_device_flags; // general purpose flags; see DCB_DEV_FLAGS
+ uint32_t DCB_device_flags2; // general purpose flags; see DCB_DEV_FLAGS2
+ uint32_t DCB_Partition_Start; // partition start sector
+ uint32_t DCB_track_table_ptr; // address of track table buffer
+ // for ioctls
+ uint32_t DCB_bds_ptr; // DOS BDS corresp. to this DCB
+ // (logical DCB's only)
+ uint32_t DCB_Reserved1; // reserved; must be zero
+ uint32_t DCB_Reserved2; // reserved; must be zero
+ uint8_t DCB_apparent_blk_shift; // log of apparent_blk_size
+ uint8_t DCB_partition_type; // partition type
+ uint16_t DCB_sig; // padding and signature
+ uint8_t DCB_device_type; // device type; see below
+ uint32_t DCB_Exclusive_VM; // exclusive access handle
+ uint8_t DCB_disk_bpb_flags; // BPB flags; see below
+ uint8_t DCB_cAssoc; // # of assoc. logical drives
+ uint8_t DCB_Sstor_Host; // indicates a sstor host volume
+ uint16_t DCB_user_drvlet; // userdriveletter settings or 0xff
+ uint16_t DCB_Reserved3; // reserved; must be zero
+ uint32_t DCB_Reserved4; // reserved; must be zero
+} DCB_COMMON, *PDCB_COMMON;
+
+typedef _Packed struct _DCB_BLOCKDEV {
+ uint32_t DCB_BDD_Next;
+ uint8_t DCB_BDD_BD_Major_Version; // INIT <BDD_BD_Major_Ver>
+ uint8_t DCB_BDD_BD_Minor_Version; // INIT <BDD_BD_Minor_Ver>
+ uint8_t DCB_BDD_Device_SubType;
+ uint8_t DCB_BDD_Int_13h_Number;
+ uint32_t DCB_BDD_flags; // was BDD_Flags
+ char * DCB_BDD_Name_Ptr;
+
+ // Apparent geometry as seen by TSD and above
+ uint64_t DCB_apparent_sector_cnt; // # of sectors
+ uint32_t DCB_apparent_blk_size; // blk size of device
+ uint32_t DCB_apparent_head_cnt; // # of heads
+ uint32_t DCB_apparent_cyl_cnt; // # of cylinders
+ uint32_t DCB_apparent_spt; // # of secs/trk
+
+ uint32_t DCB_BDD_Sync_Cmd_Proc;
+ uint32_t DCB_BDD_Command_Proc;
+ uint32_t DCB_BDD_Hw_Int_Proc; // INIT <0>
+ uint32_t DCB_BDP_Cmd_Queue_Ascending;
+ uint32_t DCB_BDP_Cmd_Queue_Descending;
+ uint32_t DCB_BDP_Current_Flags;
+ uint32_t DCB_BDP_Int13_Param_Ptr;
+ uint32_t DCB_BDP_Current_Command;
+ uint32_t DCB_BDP_Current_Position[2];
+ uint32_t DCB_BDP_Reserved[5];
+ uint32_t DCB_fastdisk_bdd; // set for DCBs created when a
+ // fastdisk regs with the
+ // blockdev BDD for it. Else 0.
+} DCB_BLOCKDEV, *PDCB_BLOCKDEV;
+
+
+typedef _Packed struct DCB {
+ DCB_COMMON DCB_cmn; // common information
+ uint32_t DCB_max_xfer_len; // maximum transfer length
+
+ // Actual geometry data (as seen below the TSD)
+ uint64_t DCB_actual_sector_cnt; // number of sectors
+ uint32_t DCB_actual_blk_size; // actual block size of the device
+ uint32_t DCB_actual_head_cnt; // number of heads
+ uint32_t DCB_actual_cyl_cnt; // number of cylinders
+ uint32_t DCB_actual_spt; // number of sectors per track
+
+ void* DCB_next_ddb_dcb; // link to next DCB on DDB chain
+ void* DCB_dev_node; // address of dev node for device
+ uint8_t DCB_bus_type; // Bus type; see below
+ uint8_t DCB_bus_number; // channel (cable) within adapter
+ uint8_t DCB_queue_freeze; // queue freeze depth counter
+ uint8_t DCB_max_sg_elements; // max # s/g elements; see below
+
+ // Volume tracking layer use only
+ uint8_t DCB_io_pend_count; // # of pending requests for DCB
+ uint8_t DCB_lock_count; // # of LOCK MEDIA commands
+
+ // SCSI
+ uint16_t DCB_SCSI_VSD_FLAGS; // Flags for SRB builder
+ uint8_t DCB_scsi_target_id; // SCSI target ID
+ uint8_t DCB_scsi_lun; // SCSI logical unit number
+ uint8_t DCB_scsi_hba; // prot-driver-relative adapter #
+ uint8_t DCB_max_sense_data_len; // Maximum sense length
+ uint16_t DCB_srb_ext_size; // miniport srb extension length
+
+ uint8_t DCB_inquiry_flags[8]; // Device inquiry flags
+ char DCB_vendor_id[8]; // Vendor ID string
+ char DCB_product_id[16]; // Product ID string
+ char DCB_rev_level[4]; // Product revision level
+ char DCB_port_name[8]; //
+ uint8_t DCB_current_unit; // current unit #; see below
+
+ // Volume tracking layer use only
+ uint32_t DCB_blocked_iop; // address of requests for an
+ // inactive volume
+
+ uint32_t DCB_vol_unlock_timer; // unlock timer handle
+ uint8_t DCB_access_timer; // time measure between accesses
+
+ // Volume tracking layer use only
+ uint8_t DCB_Vol_Flags; // Volume tracking; see below
+
+ uint8_t DCB_q_algo; // algorithm index; see below
+ uint8_t DCB_unit_on_ctl; // 0-based device number on ctlr
+ uint32_t DCB_Port_Specific; // bytes for port driver use
+ uint32_t DCB_spindown_timer; // timer for drive spin down
+
+ DCB_BLOCKDEV DCB_bdd;
+} DCB, *PDCB;
+
+/** Contains information about a calldown entry. */
+typedef _Packed struct DCB_cd_entry {
+ IO_callback DCB_cd_io_address; // address of request routine
+ uint32_t DCB_cd_flags; // demand bits; see below
+ struct DDB *DCB_cd_ddb; // driver's DDB pointer
+ struct DCB_cd_entry *DCB_cd_next; // address of next cd entry
+ uint16_t DCB_cd_expan_off; // offset of expansion area
+ uint8_t DCB_cd_layer_flags; // flags for layer's use
+ uint8_t DCB_cd_lgn; // load group number
+} DCB_cd_entry, *PDCB_cd_entry;
+
+enum DCB_BUS {
+ DCB_BUS_ESDI = 0,
+ DCB_BUS_SCSI = 1,
+ DCB_BUS_NEC = 2,
+ DCB_BUS_SMART = 3,
+ DCB_BUS_ABIOS = 4
+};
+
+enum DCB_TSD_FLAGS {
+ /** Don't trust the BPB */
+ DCB_TSD_INVALID_PARTITION = 1,
+ /** Bad MBR (physical device only) */
+ DCB_TSD_BAD_MBR = 1,
+ /** User drive letters checked */
+ DCB_TSD_USER_LETTERS_CHECKED = (1 << 1),
+ /** User drive letters checked first time */
+ DCB_TSD_FIRST_USER_CHECK = (1 << 2),
+ /** User set the a drive letter range */
+ DCB_TSD_USER_SET = (1 << 4),
+ /** User int 13 disabled */
+ DCB_TSD_NO_USER_INT13 = 0x020,
+ /** Protmode only int 13 */
+ DCB_TSD_PROTMODE_INT_13 = (1 << 6),
+ /** Reserved drive letter */
+ DCB_TSD_NONEXIST_PARTITION = (1 << 7),
+ /** Recommend BPB set from PBR by TSD (from int 41 or int 46) */
+ DCB_TSD_MBPB_PBR = 0x0100,
+ /** Bit shift for recommended BPB initialized */
+ DCB_TSD_RBPB_INIT = (1 << 9),
+ /** Media BPB initialized */
+ DCB_TSD_MBPB_INIT = 0x0400,
+ /** Apparent set from primary PBR */
+ DCB_TSD_APPARENT_PBR = 0x0800,
+ /** Apparent set from ROM data */
+ DCB_TSD_APPARENT_ROM = 0x1000,
+ /** Actual set from apparent by TSD */
+ DCB_TSD_ACTUAL_SET = 0x2000,
+ /** Actual geometry set prior to TSD */
+ DCB_TSD_ACTUAL_PRE_SET = 0x4000,
+ /** Apparent geometry set prior to TSD */
+ DCB_TSD_APPARENT_PRE_SET = 0x8000
+};
+
+enum DCB_DEV_FLAGS
+{
+ /** indicates drive supports spindown command */
+ DCB_DEV_SPINDOWN_SUPPORTED_BIT = 31,
+ DCB_DEV_SPINDOWN_SUPPORTED = (1 << DCB_DEV_SPINDOWN_SUPPORTED_BIT),
+
+ /* indicates drive is currently spun down */
+ DCB_DEV_SPUN_DOWN_BIT = 30,
+ DCB_DEV_SPUN_DOWN = (1 << DCB_DEV_SPUN_DOWN_BIT),
+
+ /* port driver use only - indicates I/O has been received for this DCB */
+
+ DCB_DEV_IO_ACTIVE_BIT = 29,
+ DCB_DEV_IO_ACTIVE = (1 << DCB_DEV_IO_ACTIVE_BIT),
+
+ /* this device has been configured as an int 13 drive */
+
+ DCB_DEV_INT13_CONFIGURED_BIT = 28,
+ DCB_DEV_INT13_CONFIGURED = (1 << DCB_DEV_INT13_CONFIGURED_BIT),
+
+ /* This DCB cannot handle ASPI or CAM requests */
+
+ DCB_DEV_NO_ASPICAM_BIT = 27,
+ DCB_DEV_NO_ASPICAM = (1 << DCB_DEV_NO_ASPICAM_BIT),
+
+ /* this device is going away soon */
+ DCB_DEV_REMOVAL_PENDING_BIT = 26,
+ DCB_DEV_REMOVAL_PENDING = (1 << DCB_DEV_REMOVAL_PENDING_BIT),
+
+ /* logical drives for this int 13 device are controlled by the mapper */
+ DCB_DEV_RMM_INT13_BIT = 25,
+ DCB_DEV_RMM_INT13 = (1 << DCB_DEV_RMM_INT13_BIT),
+
+ /* this device has been processed by a TSD */
+ DCB_DEV_TSD_PROCESSED_BIT = 24,
+ DCB_DEV_TSD_PROCESSED = (1 << DCB_DEV_TSD_PROCESSED_BIT),
+
+ /* this is an equivlaent of IRS_DRV_SINGLE_FLOPPY. This indicates that */
+ /* the dmd_a_b_toggling is not set in the floppy dcb. */
+ DCB_DEV_SINGLE_FLOPPY = 0x00800000,
+
+ /* this device must be controlled by a real mode driver, and should not */
+ /* be taken over in protected mode */
+ DCB_DEV_REAL_MODE = 0x00400000,
+
+ /* current queing direction is descending - used by port drivers or IOS if */
+ /* DCB_SERIAL_CMD flag is set */
+ DCB_DEV_QUE_DESCEND = 0x00200000,
+
+ /* hardware indicated media change */
+ DCB_DEV_MEDIA_CHANGED = 0x00100000,
+
+ /* bit indicates the disk geometry has become invalid. */
+ DCB_DEV_INVALID_GEOM = 0x00080000,
+
+ /* If this bit is set, it specifies not have the volume tracking layer do
+ * the 2 second timing stuff for media change. This bit is typically set
+ * if DCB_DEV_SYNC_MED_CHG_SUPPORT is set.
+ */
+ DCB_DEV_INHIBIT_SW_TIMED_SUPPORT = 0x00040000, /* drive is in read only mode */
+
+ /* drive has h/w support to indicate by interrupt that disk door has been
+ * popped open, etc.
+ */
+ DCB_DEV_ASYNC_MED_CHG_SUPPORT = 0x00020000,
+
+ /* drive has h/w sync med. chg supp. -- when u first access a drive with
+ * media changed, drive returns an error.
+ */
+ DCB_DEV_SYNC_MED_CHG_SUPPORT = 0x00010000,
+
+ DCB_DEV_PHYSICAL = 0x00008000, /* on = physical */
+ DCB_DEV_LOGICAL = 0x00004000, /* on = logical */
+ DCB_DEV_LOGICAL_BIT = 14,
+
+ DCB_DEV_RMM = 0x00002000, /* on = real mode mapper DCB*/
+ DCB_DEV_RMM_BIT = 13,
+
+ DCB_DEV_UNCERTAIN_MEDIA = 0x00001000, /* media may have been changed */
+ DCB_DEV_ROM_CFG_REQ = 0x00000800, /* tsd must use rom chain data */
+
+ DCB_DEV_MUST_CONFIGURE = 0x00000400,/* set at create dcb time */
+ /* cleared after all layers */
+ /* get the config aep for */
+ /* this DCB. */
+
+ DCB_DEV_SSTOR_VOL = 0x00000200, /* set if this is SSTOR volume */
+
+ DCB_DEV_PAGING_DEVICE = 0x00000100, /* indicates that the paging drive */
+ /* is on this DCB */
+
+ DCB_CHAR_DEVICE = 0x00000080, /* indicates that this is a char dev*/
+ /*
+ * The foll. DCB_DEV_I13_IN_PROGRESS flag is used in conjunction with RMM
+ * and the global DCB. This flag is set when an int 13 in progress, in
+ * iosint13.asm, and cleared when the int 13 is done.
+ */
+
+ DCB_DEV_I13_IN_PROGRESS = 0x00000040,
+
+ /** Cached device */
+ DCB_DEV_CACHE = 0x00000020 ,
+ /** Serial commands */
+ DCB_DEV_SERIAL_CMD = 0x00000010,
+ /** Remote device */
+ DCB_DEV_REMOTE = 0x00000008,
+ /* Removable media */
+ DCB_DEV_REMOVABLE = 0x00000004 ,
+ DCB_DEV_REMOVABLE_BIT = 2,
+ /** Writeable media */
+ DCB_DEV_WRITEABLE = 0x00000002,
+ /** Int 13h drive */
+ DCB_DEV_INT13_DRIVE = 0x00000001,
+};
+
+enum DCB_DEV2_FLAGS {
+ DCB_DEV2_THREAD_LOCK = 0x00000001,
+ DCB_DEV2_THREAD_LOCK_BIT = 0,
+
+ DCB_DEV2_ATAPI_DEVICE = 0x00000002,
+ DCB_DEV2_ATAPI_DEVICE_BIT = 1,
+
+ DCB_DEV2_DMF_DISK = 0x00000004, /* indicates current media is DMF */
+ DCB_DEV2_DMF_DISK_BIT = 2,
+
+ DCB_DEV2_I13_COPY_PROT = 0x00000008,/* indicates int 13 copy protection */
+ DCB_DEV2_I13_COPY_PROT_BIT = 3, /* access to one of the floppies */
+
+ DCB_DEV2_FAIL_FORMAT = 0x00000010, /* indicates that we cannot allow format*/
+ DCB_DEV2_FAIL_FORMAT_BIT = 4,
+
+ DCB_DEV2_FLOPTICAL = 0x00000020, /* indicates device is floptical */
+ DCB_DEV2_FLOPTICAL_BIT = 5,
+};
+
+enum DCB_TYPE {
+ DCB_type_disk = 0x00, /* All Direct Access Devices -- non-removable */
+ DCB_type_tape = 0x01, /* Sequencial Access Devices */
+ DCB_type_printer = 0x02, /* Printer Device */
+ DCB_type_processor = 0x03, /* Processor type device */
+ DCB_type_worm = 0x04, /* Write Once Read Many Device */
+ DCB_type_cdrom = 0x05, /* CD ROM Device */
+ DCB_type_scanner = 0x06, /* Scanner Device */
+ DCB_type_optical_memory = 0x07, /* some Optical disk */
+ DCB_type_changer = 0x08, /* Changer device e.g. juke box */
+ DCB_type_comm = 0x09, /* Communication devices */
+ DCB_type_floppy = 0x0A, /* devices like floppy */
+ DCB_type_optical_nec = 0x84 /* NEC 5.25" optical disk */
+};
+
+enum DCB_dmd_flags {
+ DCB_dmd_srb_cdb = 0x00000001, /* there must be an srb and cdb for */
+ /* each ior. */
+ DCB_dmd_rsrv_1 = 0x00000002, /* reserved - must be zero. */
+
+ DCB_dmd_logical = 0x00000004, /* media address must be logical and*/
+ /* dcb must be for a logical device.*/
+
+ DCB_dmd_physical = 0x00000008, /* media adresses must be physical */
+ /* and dcb must be for a physical */
+ /* device. */
+
+ DCB_dmd_small_memory = 0x00000010, /* data buffers must reside in */
+ /* in low 16M. */
+
+ DCB_dmd_rsrv_2 = 0x00000020, /* reserved - must be zero. */
+
+ DCB_dmd_rsrv_3 = 0x00000040, /* reserved - must be zero. */
+ DCB_dmd_rsrv_4 = 0x00000080, /* reserved - must be zero. */
+
+ DCB_dmd_not_512 = 0x00000100, /* sector size on the media is not */
+ /* 512 bytes */
+
+ DCB_dmd_word_align = 0x00000200, /* data buffers must be word */
+ /* aligned. */
+
+ DCB_dmd_dword_align = 0x00000400, /* data buffers must be double word */
+ /* aligned. */
+
+ DCB_dmd_phys_sgd = 0x00000800, /* scatter/gather descriptors must */
+ /* contain physical addresses (they */
+ /* will contain linear addresses if */
+ /* this demand bit is not set). */
+
+ DCB_dmd_phys_sgd_ptr = 0x00001000, /* the pointer to the first */
+ /* scatter/gather descriptor */
+ /* (IOR_sgd_lin_phys) must be a */
+ /* physical address (it will be a */
+ /* linear addresses if this demand */
+ /* bit is not set). */
+
+ /* If foll. bit set, single floppy drive system -- voltrack needs to do
+ * the toggling between A: and B: when accesses to made to these drive
+ * letters. */
+ DCB_dmd_do_a_b_toggling = 0x00002000,
+
+ DCB_dmd_query_remov = 0x00004000, /* lower level must be called to */
+ /* check if media is removeable/has */
+ /* changed. */
+
+ DCB_dmd_request_sns = 0x00008000, /* port driver cannot perform autosense */
+ DCB_dmd_lock_unlock_media = 0x00010000, /* media supports software locking */
+ DCB_dmd_load_eject_media = 0x00020000, /* media supports electronic eject */
+ DCB_dmd_clear_media_chg = 0x00040000, /* command to clear media change */
+ DCB_dmd_serialize_bit = 19, /* layer requires request serialization */
+ DCB_dmd_serialize = 1 << DCB_dmd_serialize_bit,
+ DCB_dmd_prot_mode = 0x00100000, /* override real mode checks for */
+ /* this device i.e. ATAPI cdrom */
+ DCB_dmd_no_xclusive = 0x00200000, /* indicates exclusive access is */
+ /* not required for abs write */
+ DCB_dmd_pageability = 0x00400000, /* indicates that access to this volume */
+ /* can cause paging */
+
+ DCB_dmd_rsrv = DCB_dmd_rsrv_1+DCB_dmd_rsrv_2+DCB_dmd_rsrv_3+DCB_dmd_rsrv_4
+};
+
+
+
+// DDB ================
+
+typedef _Packed struct DDB { //
+ uint32_t DDB_phys_addr; // Physical address of ddb
+ uint32_t DDB_Next_DDB; // next DDB on DVT_DDB chain
+ uint32_t DDB_Next_DDB_init; // next DDB on DVT_DDB_init chain
+ uint32_t DDB_dcb_ptr; // address of first DCB owned by this DDB
+ uint8_t DDB_number_buses; // # of buses supported by the adapter
+ uint8_t DDB_ios_flags; // IOS flags; see below
+ uint16_t DDB_sig; // DDB signature; also padding
+ void* DDB_dvt; // address of this DDB's DVT
+ void* DDB_devnode_ptr; // address of devnode for device
+ void* DDB_reserved; // reserved; must be zero
+} DDB , *PDDB;
+
+
+// DRP ================
+
+typedef _Packed struct DRP {
+ char DRP_eyecatch_str[8]; // eye catcher string
+ uint32_t DRP_LGN; // load group number; see below
+ void* DRP_aer; // addr of asynchronous event routine
+ struct ILB *DRP_ilb; // address of ILB; see below
+ char DRP_ascii_name[16]; // driver name; see below
+ uint8_t DRP_revision; // driver revision
+ uint32_t DRP_feature_code; // feature code; see below
+ uint16_t DRP_if_requirements; // I/F requirements; see below
+ uint8_t DRP_bus_type; // type of I/O bus; see below
+ uint16_t DRP_reg_result; // registration result; see below
+ uint32_t DRP_reference_data; // data to pass with AEP_INITIALIZE
+ uint8_t DRP_reserved1[2]; // reserved
+ uint32_t DRP_reserved2[1]; // reserved; must be zero
+} DRP, *PDRP;
+
+enum DRP_IF {
+ DRP_IF_ISA = 0x01,
+ DRP_IF_EISA = 0x02,
+ DRP_IF_MCA = 0x04,
+ DRP_IF_STD = 0xFF
+};
+
+enum DRP_LGN {
+ /* Installable File System layer. */
+ DRP_FSD = 1 << 0x02,
+ /** Other port drivers. */
+ DRP_MISC_PD = 1 << 0x13
+};
+
+enum DRP_reg_result {
+ /* Driver should remain resident */
+ DRP_REMAIN_RESIDENT = 1,
+ /* Driver should minimize */
+ DRP_MINIMIZE = 2,
+ /* Driver should not load */
+ DRP_ABORT = 3,
+ /* bad layer number - abort driver */
+ DRP_INVALID_LAYER = 4
+};
+
+
+// ILB ==================
+
+typedef _Packed struct ILB {
+ /* service routine */
+ void __cdecl (*ILB_service)(struct ISP *pisp);
+ void __cdecl (*ILB_dprintf)(const char *format, ...);
+ /** waits for 1/10 seconds. */
+ void __cdecl (*ILB_Wait_10th_Sec)(void);
+ void* ILB_internal_request; // addr of request routine
+ void* ILB_io_criteria_rtn; // addr of IOR criteria routine
+ void* ILB_int_io_criteria_rtn; // addr of IOP criteria routine
+ uint32_t ILB_dvt; // offset to addr of driver's DVT
+ uint32_t ILB_ios_mem_virt; // addr of IOS memory pool
+ void __cdecl (*ILB_enqueue_iop)(struct IOP *iop, struct DCB *dcb);
+ struct IOP * __cdecl (*ILB_dequeue_iop)(struct DCB *dcb);
+ uint32_t ILB_reserved_1; // reserved; must be zero
+ uint32_t ILB_reserved_2; // reserved; must be zero
+ uint16_t ILB_flags; // flags
+ uint8_t ILB_driver_numb; // number of calls to AEP_INITIALIZE
+ uint8_t ILB_reserved_3; // reserved; must be zero
+} ILB, *PILB;
+
+// IOR =================
+
+typedef _Packed struct type_sdeffsd_req_usage { /* */
+ uint16_t _IOR_ioctl_drive;
+ uint16_t _IOR_ioctl_function ;
+ uint32_t _IOR_ioctl_control_param ;
+ uint32_t _IOR_ioctl_buffer_ptr;
+ uint32_t _IOR_ioctl_client_params;
+ uint32_t _IOR_ioctl_return;
+} _type_sdeffsd_req_usage;
+
+typedef _Packed union urequestor_usage { /* */
+ uint32_t _IOR_requestor_usage[5];
+ struct type_sdeffsd_req_usage sdeffsd_req_usage;
+} _urequestor_usage;
+
+typedef void (* CMDCPLT)(void);
+
+typedef _Packed struct _IOR {
+ uint32_t IOR_next; // client link; see below
+ uint16_t IOR_func; // function; see below
+ uint16_t IOR_status; // request status; see below
+ uint32_t IOR_flags; // request flags; see below
+ CMDCPLT IOR_callback; // address of callback; see below
+ uint64_t IOR_start_addr; // starting address; see below
+ uint32_t IOR_xfer_count; // # of sectors/bytes; see below
+ void* IOR_buffer_ptr; // client buffer; see below
+ uint32_t IOR_private_client; // BlockDev/IOS client reserved
+ uint32_t IOR_private_IOS; // reserved space for IOS
+ uint32_t IOR_private_port; // private area for port driver
+ union urequestor_usage _ureq; // requestor usage; see below
+ uint32_t IOR_req_req_handle; // request handle; see below
+ uint32_t IOR_req_vol_handle; // media handle; see below
+ uint32_t IOR_sgd_lin_phys; // first physical SGD; see below
+ uint8_t IOR_num_sgds; // number of physical SGDs
+ uint8_t IOR_vol_designtr; // drive letter; see below
+ uint16_t IOR_ios_private_1; // reserved by IOS to force alignment
+ uint32_t IOR_reserved_2[2]; // reserved for internal use
+} IOR, *PIOR;
+
+/** command codes for IOR_func as defined above */
+enum IOR_funcs {
+ /*
+ ** IOR_READ
+ **
+ ** Function:
+ **
+ ** Reads the # of sectors/bytes as specified in IOR_xfer_count.
+ ** See PDK documentation for specifics of parameter setting.
+ **
+ **
+ ** Notes:
+ **
+ */
+
+ IOR_READ = 0x00,
+
+ /*
+ ** IOR_WRITE
+ **
+ ** Function:
+ **
+ ** Writes the # of sectors/bytes as specified in IOR_xfer_count.
+ ** See PDK documentation for specifics of parameter setting.
+ **
+ ** Notes:
+ **
+ */
+
+ IOR_WRITE = 0x01,
+
+ /*
+ ** IOR_VERIFY
+ **
+ ** Function:
+ **
+ ** Verifies the # of sectors/bytes as specified in IOR_xfer_count.
+ **
+ ** Notes:
+ **
+ */
+
+ IOR_VERIFY = 0x02, /* verify data on device */
+
+ /*
+ ** IOR_CANCEL
+ **
+ ** Function:
+ **
+ ** Cancels the IOR referenced in ...
+ **
+ ** Notes:
+ **
+ ** Currently not supported in Chicago
+ **
+ */
+
+ IOR_CANCEL = 0x03,
+
+ /*
+ ** IOR_WRITEV
+ **
+ ** Function:
+ **
+ ** Writes and verifies the # of sectors/bytes as specified in IOR_xfer_count.
+ **
+ ** Notes:
+ **
+ ** Some hardware (IDE, for example) does not support this function.
+ **
+ */
+
+ IOR_WRITEV = 0x04,
+
+ /*
+ ** IOR_MEDIA_CHECK
+ **
+ ** Function:
+ **
+ ** Checks to see if media has changed, and returns status in IOR_status.
+ **
+ ** Notes:
+ **
+ ** Obsolete. Use MEDIA_CHECK_RESET below
+ **
+ */
+
+ IOR_MEDIA_CHECK = 0x05,
+
+ /*
+ ** IOR_MEDIA_CHECK_RESET
+ **
+ ** Function:
+ **
+ ** Checks to see if media has changed, and returns status in IOR_status.
+ **
+ ** Notes:
+ **
+ */
+
+ IOR_MEDIA_CHECK_RESET = 0x06,
+
+ /*
+ ** IOR_LOAD_MEDIA
+ **
+ ** Function:
+ **
+ ** Performs a LOAD MEDIA operation on removable drives.
+ **
+ ** Notes:
+ **
+ */
+
+ IOR_LOAD_MEDIA = 0x07,
+
+ /*
+ ** IOR_EJECT_MEDIA
+ **
+ ** Function:
+ **
+ ** Performs an EJECT MEDIA operation on removable drives.
+ **
+ ** Notes:
+ **
+ ** For disk and CD-ROM devices, applications are queried before eject,
+ ** and the lock count is checked for zero. If one of these checks fails,
+ ** the user is queried to see if he wants to proceed.
+ */
+
+ IOR_EJECT_MEDIA = 0x08,
+
+ /*
+ ** IOR_LOCK_MEDIA
+ **
+ ** Function:
+ **
+ ** Performs a LOCK MEDIA operation on removable drives.
+ **
+ ** Notes:
+ **
+ ** LOCK and UNLOCK MEDIA calls must be synchronized, as a depth counter is
+ ** maintained by IOS.
+ */
+
+ IOR_LOCK_MEDIA = 0x09,
+
+ /*
+ ** IOR_UNLOCK_MEDIA
+ **
+ ** Function:
+ **
+ ** Performs an UNLOCK MEDIA operation on removable drives.
+ **
+ ** Notes:
+ **
+ ** LOCK and UNLOCK MEDIA calls must be synchronized, as a depth counter is
+ ** maintained by IOS. UNLOCK MEDIA is delayed by about 5 seconds.
+ */
+
+ IOR_UNLOCK_MEDIA = 0x0A,
+
+ /*
+ ** IOR_REQUEST_SENSE
+ **
+ ** Function:
+ **
+ ** Performs an REQUEST SENSE operation on removable drives.
+ **
+ ** Notes:
+ **
+ ** Currently supported for SCSI devices only.
+ **
+ */
+
+ IOR_REQUEST_SENSE = 0x0B,
+
+ /*
+ ** IOR_COMPUTE_GEOM
+ **
+ ** Function:
+ **
+ ** Results in a reexamination of volume and device characteristics.
+ **
+ ** Notes:
+ **
+ ** When this command is received, each IOS layer assumes the device has
+ ** changed, and performs whatever initialization required. For example,
+ ** the disk TSD will reexamine the partitions of a device.
+ **
+ */
+
+ IOR_COMPUTE_GEOM = 0x0C,
+
+ /*
+ ** IOR_GEN_IOCTL
+ **
+ ** Function:
+ **
+ ** Function permits device-specific IOCtl's to be issued to IOS drivers.
+ ** Also supports standard Int 21 function 44xx DOS IOCtl's. See CDIOCTL.H
+ ** for examples of CD IOCtl's.
+ **
+ ** aliasing for fields used by IOCTL's -
+ ** this section gives both the normal and IOCTL names for the fields
+ **
+ ** IOR_ioctl_drive - BX from 16 bit IOCTL - usually a 1 based drive number
+ ** IOR_ioctl_function - AX from 16 bit IOCTL
+ ** IOR_ioctl_handle - useful handle? or original?
+ ** IOR_ioctl_control_param - CX zero extended to ULONG
+ ** IOR_ioctl_buffer_ptr - DS:DX mapped flat general buffer from the IOCTL
+ ** IOR_ioctl_client_params - a pointer to the parameters passed in from
+ ** the calling application the IORF_16BIT_IOCTL flag must be used to
+ ** determine the structure type. If it is a 16 bit IOCTL, the pointer
+ ** will be to the CLIENT_REGS structure. 32 bit is as of yet undef'ed
+ ** IOR_ioctl_return - return value to be mapped to the IOCTL caller on exit
+ ** IOR_ioctl_buffer_length - size of buffer pointed to by
+ ** IOR_ioctl_buffer_ptr (not used by int 21 440d IOCTL calls)
+ **
+ ** Notes:
+ **
+ */
+
+ IOR_GEN_IOCTL = 0x0D,
+
+ /*
+ ** IOR_FORMAT
+ **
+ ** Function:
+ **
+ ** Allows issuance of a low level format packet as defined in the int 13
+ ** specification.
+ **
+ ** Notes:
+ **
+ ** IOR_format_address_field => low level sector map
+ ** See IOP.H IOP_FORMAT_XXX fields for additional information.
+ */
+
+ IOR_FORMAT = 0x0E,
+
+ /*
+ ** IOR_SCSI_PASS_THROUGH
+ **
+ ** Function:
+ **
+ ** Each layer passes this request down with modification to the SCSI port
+ ** driver.
+ **
+ ** Notes:
+ **
+ ** The IOR must contain a pointer to a valid SRB. Also, "ebx" must point
+ ** to the BDD portion of the DCB when IOS_SendCommand is called with a
+ ** passthrough request.
+ */
+
+ IOR_SCSI_PASS_THROUGH = 0x0F,
+
+ /*
+ ** IOR_CLEAR_QUEUE
+ **
+ ** Function:
+ **
+ ** Callback of request indicates all requests issued prior to this request
+ ** have been completed.
+ **
+ ** Notes:
+ */
+
+ IOR_CLEAR_QUEUE = 0x10,
+
+ /*
+ ** IOR_DOS_RESET
+ **
+ ** Function:
+ **
+ ** Issued in response to an int 13 reset by the int 13 vxd
+ **
+ ** Notes:
+ **
+ ** Currently used only by and for the IOS floppy driver.
+ */
+
+ IOR_DOS_RESET = 0x11,
+
+ /*
+ ** IOR_SCSI_REQUEST
+ **
+ ** Function:
+ **
+ ** generic function that indicates the request is for SCSI.
+ **
+ ** Notes:
+ **
+ ** SRB field in IOR must be valid. Normally used by IOS layers to send
+ ** internal requests to SCSI devices.
+ */
+
+ IOR_SCSI_REQUEST = 0x12,
+
+ /*
+ ** IOR_SET_WRITE_STATUS
+ **
+ ** Function:
+ **
+ ** results in the correct setting of DCB_DEV_WRITABLE flag in the DCB if
+ ** SUCCESS status is returned.
+ **
+ ** Notes:
+ **
+ ** Currently only supported by the IOS floppy driver.
+ */
+
+ IOR_SET_WRITE_STATUS = 0x22,
+
+ /*
+ ** IOR_RESTART_QUEUE
+ **
+ ** Function:
+ **
+ ** "Placeholder" function that results in restarting of the queue provided
+ ** by the IOS queuing services. Has no other effect.
+ **
+ ** Notes:
+ **
+ ** Should be sent after the DCB_QUEUE_FREEZE field has been decremented to
+ ** zero.
+ */
+
+ IOR_RESTART_QUEUE = 0x23,
+
+ /*
+ ** IOR_ABORT_QUEUE
+ **
+ ** Function:
+ **
+ ** Causes each layer to call back each queued request with status of
+ ** IORS_CANCELED
+ **
+ ** Notes:
+ **
+ */
+
+ IOR_ABORT_QUEUE = 0x24,
+
+ /*
+ ** IOR_SPIN_DOWN
+ **
+ ** Function:
+ **
+ ** Causes the specified drive to be spun down.
+ **
+ ** Notes:
+ **
+ */
+
+ IOR_SPIN_DOWN = 0x25,
+
+ /*
+ ** IOR_SPIN_UP
+ **
+ ** Function:
+ **
+ ** Causes the specified drive to be spun up.
+ **
+ ** Notes:
+ **
+ */
+
+ IOR_SPIN_UP = 0x26,
+
+ /*
+ ** IOR_FLUSH_DRIVE
+ **
+ ** Function:
+ **
+ ** Causes layer drivers to flush out dirty data.
+ **
+ ** Notes:
+ **
+ ** This is a synchronous function and should not be called at
+ ** event/interrupt time.
+ **
+ */
+
+ IOR_FLUSH_DRIVE = 0x27,
+
+
+ /*
+ ** IOR_FLUSH_DRIVE_AND_DISCARD
+ **
+ ** Function:
+ **
+ ** Causes layer drivers to flush out dirty data and then dicard cached
+ ** data.
+ **
+ ** Notes:
+ **
+ ** This is a synchronous function and should not be called at
+ ** event/interrupt time.
+ **
+ */
+
+ IOR_FLUSH_DRIVE_AND_DISCARD = 0x28,
+
+
+ /*
+ ** IOR_FSD_EXT
+ **
+ ** Function:
+ **
+ ** Sends private command from an IOS client to the FSD extension
+ ** layer (DRP_FSD_EXT_1 or DRP_FSD_EXT_2) such as a Compressed
+ ** Volume Manager (CVM).
+ **
+ ** Notes:
+ **
+ ** This call is similar in intent and usage to IOR_GEN_IOCTL;
+ ** however, it can be used in the swapper path, so, in general,
+ ** the handling driver should not touch pageable code or data.
+ **
+ */
+
+ IOR_FSD_EXT = 0x29,
+
+ /*
+ ** IOR_FLOPTICAL_MODE_SENSE
+ **
+ ** Function:
+ **
+ ** Issues MODE SENSE command to page 0x2e, as is required to indicate to the
+ ** floptical that it is supported.
+ **
+ ** Notes:
+ **
+ ** Don't use this command, as it is used by the system.
+ **
+ */
+
+ IOR_FLOPTICAL_MODE_SENSE = 0x2A,
+
+};
+
+/** IOR status codes for IOR_status */
+
+enum IOR_STATUS {
+ IORS_SUCCESS = 0x00, /* command completed successfully */
+
+ IORS_SUCCESS_WITH_RETRY = 0x01, /* successful, but retries required */
+
+ IORS_SUCCESS_WITH_ECC = 0x02, /* ditto, but with ECC */
+
+ IORS_INVALID_SECTOR = 0x10, /* old blockdev error code for */
+ /* requests on an invalid sector */
+ /* number */
+ IORS_CANCELED = 0x11, /* command was canceled */
+
+ IORS_CMD_IN_PROGRESS = 0x12, /* command is in progress, and can't be canceled */
+
+ IORS_INVALID_CMD_PTR = 0x13, /* cancel of an invalid command */
+
+ IORS_MEDIA_ERROR = 0x14, /* media failure */
+
+ IORS_DEVICE_ERROR = 0x15, /* error on recalib. drive, etc. */
+
+ IORS_INVALID_COMMAND = 0x16, /* Command not supported or invalid */
+
+ IORS_NO_DEVICE = 0x17, /* physical or logical device nonexistant */
+
+ IORS_NO_MEDIA = 0x18, /* media removed from device */
+
+ IORS_UNCERTAIN_MEDIA = 0x19, /* media may have changed */
+
+ IORS_UNREC_ERROR = 0x1A, /* un-recoverable error */
+
+ IORS_HW_FAILURE = 0x1B, /* general hardware failure */
+
+ IORS_UNFORMATTED_MEDIA = 0x1C, /* unformatted media */
+
+ IORS_MEMORY_ERROR = 0x1D, /* error allocating DMA buffer space for I/O */
+
+ IORS_TIME_OUT = 0x1E, /* device timed out */
+
+ IORS_WRITE_PROTECT = 0x1F, /* write protect error */
+
+ IORS_NOT_READY = 0x20, /* device not ready */
+
+ IORS_BUSY = 0x21, /* device is busy */
+
+ IORS_VOL_NOT_LOCKED = 0x22, /* volume received unlock without lk*/
+
+ IORS_VOL_LOCKED = 0x23, /* eject received with drive locked */
+
+ IORS_VOL_NOT_REMOVABLE = 0x24, /* removable request to non-remov drive */
+
+ IORS_VOL_IN_USE = 0x25, /* volume is in use - used for eject*/
+
+ IORS_LOCK_COUNT_EXCEEDED = 0x26, /* volume lock count exceeded */
+
+ IORS_VALID_EJECT_FAILED = 0x27, /* eject command was not accepted */
+
+ IORS_ILLEGAL_ACCESS_MODE = 0x28, /* cdrom read on audio or attempt */
+ /* play on data */
+
+ IORS_LOCK_VIOLATION = 0x29, /* illegal access to locked device */
+
+ IORS_WRONG_MEDIA = 0x2a, /* wrong media in drive */
+
+ IORS_OUT_OF_SPACE = 0x2b, /* no space on media */
+
+ IORS_BIG_IO_BREAKUP_FAILED = 0x2c,/* attempt to split big I/O barfed */
+
+ IORS_ERROR_DESIGNTR = 0x10, /* bit indicating error occurred */
+
+ IORS_INVALID_PARM = IORS_ERROR_DESIGNTR, /* bit indicating error occurred */
+};
+
+enum IOR_FLAGS {
+
+ IORF_HIGH_PRIORITY_BIT=0, /* binary priority indication */
+ IORF_HIGH_PRIORITY = (1 << IORF_HIGH_PRIORITY_BIT), /* 0x0001 */
+
+ IORF_SCATTER_GATHER_BIT=1, /* bit indicating BDCB physical SGD */
+ /* list present */
+ IORF_SCATTER_GATHER = (1 << IORF_SCATTER_GATHER_BIT), /* 0x0002 */
+
+ IORF_DONT_CACHE_BIT=2, /* bit indicating that the BDCB data*/
+ /* should not be cached */
+ IORF_DONT_CACHE = (1 << IORF_DONT_CACHE_BIT), /* 0x0004 */
+
+ IORF_BYPASS_VOLTRK=0x0008, /* request will not be volume trked */
+ IORF_BYPASS_VOLTRK_BIT=3,
+
+ IORF_16BIT_IOCTL=0x0010, /* if true, 16 bit IOCTL calling */
+ /* app, else 32 bit ioctl */
+ IORF_16BIT_IOCTL_BIT=4,
+
+ IORF_SWAPPER_IO=0x0020, /* request is from the swapper */
+ IORF_SWAPPER_IO_BIT=5,
+
+ IORF_DOUBLE_BUFFER=0x0040, /* indicates that the request is to */
+ /* be completed via double */
+ /* buffering. client must */
+ /* ensure that both the sgd buffer */
+ /* pointers and IOR_sgd_lin_phys */
+ /* are virtual pointers. */
+ IORF_DOUBLE_BUFFER_BIT=6,
+
+ IORF_SYNC_CMD_DONE=0x0080, /* used by IOS only to indicate */
+ /* completion */
+ /* of synchronous command */
+ IORF_SYNC_CMD_DONE_BIT=7,
+
+ IORF_SYNC_COMMAND=0x0100, /* indicates synchronous command */
+ /* (complete before return from IOS */
+ IORF_SYNC_COMMAND_BIT=8,
+
+ IORF_CHAR_COMMAND=0x0200, /* indicates transfer counts and */
+ /* scatter/gather buffer sizes are */
+ /* byte values */
+ IORF_CHAR_COMMAND_BIT=9,
+
+ IORF_VERSION_002=0x0400, /* indicates use of extended BCB */
+ /* (IOR) format for request */
+ IORF_VERSION_002_BIT=10,
+
+ IORF_BYPASS_QUEUE=0x0800, /* request should bypass IOS queuing*/
+ /* internal IOS driver use only */
+ IORF_BYPASS_QUEUE_BIT=11,
+
+ IORF_BLOCKDEV_EMULATE=0x1000, /* this request started as a BCB */
+ IORF_BLOCKDEV_EMULATE_BIT=12,
+
+ IORF_POSTPONED_VOL_OPS=0x2000,
+ IORF_POSTPONED_VOL_OPS_BIT=13,
+
+ IORF_INHIBIT_GEOM_RECOMPUTE=0x4000,
+ IORF_INHIBIT_GEOM_RECOMPUTE_BIT=14,
+ /* force inhibit of device geometry */
+ /* recomputation. */
+
+ IORF_SRB_VALID=0x8000, /* indicates that IOP_srb points to */
+ IORF_SRB_VALID_BIT=15, /* a valid SRB */
+
+ IORF_BYPASS_A_B=0x10000, /* indicates that VOLTRK should */
+ IOFR_BYPASS_A_B_BIT=16, /* bypass AB check */
+
+ IORF_QUIET_VOLTRK=0x20000, /* indicates that volume tracking */
+ IORF_QUIET_VOLTRL_BIT=17, /* just return error when wrong */
+ /* media in drive (no dialog box) */
+
+ IORF_AUDIO_DATA_READ=0x40000, /* indicates an audio data read */
+ IORF_AUDIO_DATA_READ_BIT=18,
+
+ IORF_LOGICAL_START_SECTOR=0x80000,
+ IORF_LOGICAL_START_SECTOR_BIT=19,
+ /* IOR_start_addr is logical */
+
+ IORF_PARTITION_BIAS_ADDED=0x100000,
+ IORF_PARTITION_BIAS_ADDED_BIT=20, /* set by TSD only to indicate that */
+ /* it has added in the bias, as req'd */
+ /* by IORF_LOGICAL_START_SECTOR */
+
+ IORF_DATA_IN_BIT=21, /* indicates a data read operation */
+ IORF_DATA_IN = (1 << IORF_DATA_IN_BIT),
+
+ IORF_DATA_OUT_BIT=22, /* indicates a data write operation */
+ IORF_DATA_OUT = (1 << IORF_DATA_OUT_BIT),
+
+ IORF_VOL_RETRY_BIT=23, /* indicates volume tracking retried*/
+ IORF_VOL_RETRY = (1 << IORF_VOL_RETRY_BIT),
+ /* request (internal use only) */
+
+ IORF_NO_COMPRESS_BIT=24, /* do not compress data */
+ IORF_NO_COMPRESS = (1 << IORF_NO_COMPRESS_BIT),
+
+ IORF_DIRECT_IO_BIT=25, /* E.g., INT 25h or INT 26h */
+ IORF_DIRECT_IO = (1 << IORF_DIRECT_IO_BIT),
+
+ IORF_PHYS_SGDS_BIT=26,
+ IORF_PHYS_SGDS = (1 << IORF_PHYS_SGDS_BIT),
+ /* indicates IOR_sgd_lin_phys valid */
+
+ IORF_IO_TOO_BIG_BIT=27,
+ IORF_IO_TOO_BIG = (1 << IORF_IO_TOO_BIG_BIT),
+ /* indicates I/O is too big for */
+ /* single pass (set by IOS criteria*/
+ /* code). */
+ IORF_WIN32_BIT=28,
+ IORF_WIN32 = (1 << IORF_WIN32_BIT),
+ /* indicates I/O is from 32 bit API */
+ IORF_CHAR_DEVICE_BIT=29,
+ IORF_CHAR_DEVICE = (1 << IORF_CHAR_DEVICE_BIT),
+
+ IORF_PHYS_CMD_BIT=30, /* indicates I/O is for a physical */
+ /* device */
+ IORF_PHYS_CMD = (1 << IORF_PHYS_CMD_BIT),
+
+ IORF_IDE_RESERVED_BIT=31, /* indicates I/O is for a physical */
+ /* device */
+ IORF_IDE_RESERVED = (1 << IORF_IDE_RESERVED_BIT)
+
+};
+
+// IOP =================
+
+#define IOP_CALLBACK_TABLE_DEPTH 6
+
+typedef _Packed struct IOP_callback_entry {
+ IO_callback IOP_CB_address; /* call back address */
+ uint32_t IOP_CB_ref_data; /* pointer to callback ref data */
+} IOP_callback_entry;
+
+typedef _Packed struct IOP {
+ uint32_t IOP_physical; // physical address of IOP
+ struct DCB *IOP_physical_dcb; // address of physical DCB
+ struct DCB *IOP_original_dcb; // address of DCB designated by IOR
+ uint16_t IOP_timer; // Request Control Block (RCB) current
+ // timeout value
+ uint16_t IOP_timer_orig; // original timeout value
+ DCB_cd_entry* IOP_calldown_ptr; // address of next calldown routine
+ IOP_callback_entry* IOP_callback_ptr; // address of current callback address
+ uint32_t IOP_voltrk_private; // for use by volume tracking
+ uint32_t IOP_Thread_Handle; // handle of thread originating
+ // the I/O request
+ uint32_t IOP_srb; // used by SCSI'izers to pass SRB
+ // address to next layer
+ uint32_t IOP_reserved[2]; // reserved; must be zero
+ IOP_callback_entry IOP_callback_table[IOP_CALLBACK_TABLE_DEPTH];
+ // RCB_CallBackTable
+ uint8_t IOP_format_head; // low level format data
+ uint8_t IOP_format_xfer_rate;
+ uint16_t IOP_format_track;
+ uint32_t IOP_format_num_sectors;
+ IOR IOP_ior; // I/O request descriptor
+} IOP, *PIOP;
+
+/** Let lower levels continue processing this IOP. */
+static void IopCalldown(IOP* iop) {
+ // Get who is supposed to be next driver in the chain
+ iop->IOP_calldown_ptr = iop->IOP_calldown_ptr->DCB_cd_next;
+ // Call it
+ iop->IOP_calldown_ptr->DCB_cd_io_address(iop);
+}
+
+/** Finish processing this IOP, and call back previous levels. */
+static void IopCallback(IOP* iop) {
+ iop->IOP_callback_ptr -= 1;
+ iop->IOP_callback_ptr->IOP_CB_address(iop);
+}
+
+// ISP =================
+
+typedef uint16_t IspResult; // unspecified error codes, but zero = no error
+
+typedef _Packed struct ISP {
+ uint16_t ISP_func; // function to be performed; see below
+ IspResult ISP_result; // result: zero = no error
+} ISP, *PISP;
+
+enum ISP_funcs {
+ /* create ddb */
+ ISP_CREATE_DDB = 0,
+ /* create dcb (Physical) */
+ ISP_CREATE_DCB = 1,
+ /* create iop */
+ ISP_CREATE_IOP = 2,
+ /* allocate memory */
+ ISP_ALLOC_MEM = 3,
+ /* deallocate memory */
+ ISP_DEALLOC_MEM = 4,
+ /** insert entry in calldown table */
+ ISP_INSERT_CALLDOWN = 5,
+ /* associate dcb & relative drive # */
+ ISP_ASSOCIATE_DCB = 6,
+ /* return DCB for logical volume */
+ ISP_GET_DCB = 7,
+ /** get first/next DCB */
+ ISP_GET_FIRST_NEXT_DCB = 8,
+ /* deallocate DDB */
+ ISP_DEALLOC_DDB = 9,
+ /* destroy DCB */
+ ISP_DESTROY_DCB = 10,
+ /* ask for logical-to-physical info */
+ ISP_QUERY_MATCHING_DCBS = 11,
+ /* ask if we can remove a device */
+ ISP_QUERY_REMOVE_DCB = 12,
+ /* indicate that a device has been removed */
+ ISP_DEVICE_REMOVED = 13,
+ /* indicate that a device has arrived */
+ ISP_DEVICE_ARRIVED = 14,
+ /* disassociate dcb & drive # */
+ ISP_DISASSOCIATE_DCB = 15,
+ /* pick a drive letter */
+ ISP_DRIVE_LETTER_PICK = 16,
+ /* read registry */
+ ISP_REGISTRY_READ = 17,
+ /* find LDM entry */
+ ISP_FIND_LDM_ENTRY = 18,
+ /* delete LDM entry */
+ ISP_DELETE_LDM_ENTRY = 19,
+ /* broadcast an AEP from a driver */
+ ISP_BROADCAST_AEP = 20
+};
+
+typedef _Packed struct ISP_dcb_create { //
+ ISP ISP_dcb_hdr; // Standard header
+ uint16_t ISP_dcb_size; // size of DCB to create, in bytes
+ uint32_t ISP_dcb_ptr; // address of DCB
+ uint8_t ISP_pad1[2]; // Pad to DWORD boundary
+} ISP_dcb_create, *PISP_dcb_create;
+
+static PDCB IspCreateDcb(ILB* ilb, uint16_t size) {
+ ISP_dcb_create isp = {{ISP_CREATE_DCB}};
+ isp.ISP_dcb_size = size;
+ ilb->ILB_service((PISP)&isp);
+ return isp.ISP_dcb_hdr.ISP_result != 0 ? NULL : (PDCB) isp.ISP_dcb_ptr;
+}
+
+typedef _Packed struct ISP_ddb_create { //
+ ISP ISP_ddb_hdr; // Standard header
+ uint16_t ISP_ddb_size; // size of DDB to create, in bytes
+ uint32_t ISP_ddb_ptr; // address of DDB
+ uint8_t ISP_ddb_flags; // flags; see below
+ uint8_t ISP_pad0[1]; // Pad to DWORD boundary
+} ISP_ddb_create, *PISP_ddb_create;
+
+static PDDB IspCreateDdb(ILB* ilb, uint16_t size) {
+ ISP_ddb_create isp = {{ISP_CREATE_DDB}};
+ isp.ISP_ddb_size = size;
+ ilb->ILB_service((PISP)&isp);
+ return isp.ISP_ddb_hdr.ISP_result != 0 ? NULL : (PDDB) isp.ISP_ddb_ptr;
+}
+
+typedef _Packed struct ISP_calldown_insert {
+ ISP ISP_i_cd_hdr; // Standard header
+ uint32_t ISP_i_cd_dcb; // address of DCB
+ void* ISP_i_cd_req; // address of request routine
+ uint32_t ISP_i_cd_ddb; // address of DDB
+ uint16_t ISP_i_cd_expan_len; // size of IOP expansion required by this driver
+ uint32_t ISP_i_cd_flags; // demand flags
+ uint8_t ISP_i_cd_lgn; // load group number; typically same as AEP_lgn
+ uint8_t ISP_pad4[1]; // Pad to DWORD boundary
+} ISP_calldown_insert, *PISP_calldown_insert;
+
+static IspResult IspInsertCalldown(ILB* ilb, PDCB dcb, void (*calldown)(PIOP), PDDB ddb, uint16_t expand_len, uint32_t flags, uint8_t lgn) {
+ ISP_calldown_insert isp = {{ISP_INSERT_CALLDOWN}};
+ isp.ISP_i_cd_dcb = (uint32_t) dcb;
+ isp.ISP_i_cd_req = calldown;
+ isp.ISP_i_cd_ddb = (uint32_t) ddb;
+ isp.ISP_i_cd_expan_len = expand_len;
+ isp.ISP_i_cd_flags = flags;
+ isp.ISP_i_cd_lgn = lgn;
+ ilb->ILB_service((PISP)&isp);
+ return isp.ISP_i_cd_hdr.ISP_result;
+}
+
+typedef _Packed struct ISP_AEP_BROADCAST {
+ ISP ISP_baep_hdr; // Standard header
+ void* ISP_baep_paep; // address of AEP to broadcast
+} ISP_AEP_BROADCAST,*PISP_AEP_BROADCAST;
+
+static IspResult IspBroadcastAep(ILB* ilb, PAEP paep) {
+ ISP_AEP_BROADCAST isp = {{ISP_BROADCAST_AEP}};
+ isp.ISP_baep_paep = paep;
+ ilb->ILB_service((PISP)&isp);
+ return isp.ISP_baep_hdr.ISP_result;
+}
+
+// SGD =========
+
+typedef struct _SGD {
+ uint32_t SG_buff_ptr; // address of data buffer; see below
+ uint32_t SG_buff_size; // number of bytes to transfer
+} SGD, *PSGD;
+
+#endif // IOS_H
diff --git a/w9x/inc/vmm.h b/w9x/inc/vmm.h
new file mode 100644
index 0000000..eb8f834
--- /dev/null
+++ b/w9x/inc/vmm.h
@@ -0,0 +1,161 @@
+#ifndef VMM_H
+#define VMM_H
+
+/** VMM VxD services and types.
+ * Manually generated for now. */
+
+#include <stdint.h>
+
+#define PAGESHIFT 12
+#define PAGESIZE (1 << PAGESHIFT)
+
+/** Timeout handle, see Set_*_Time_Out, Cancel_Time_Out services. */
+typedef void* HTIMEOUT;
+
+static inline unsigned short Get_VMM_Version(void);
+#pragma aux Get_VMM_Version = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 0" \
+ __parm caller [] __value [ax]
+
+static inline void Release_Time_Slice(void);
+#pragma aux Release_Time_Slice = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 56" \
+ __parm caller __modify []
+
+static inline void Time_Slice_Sleep(uint32_t ms);
+#pragma aux Time_Slice_Sleep = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 353" \
+ __parm caller [eax] __modify []
+
+typedef void (Global_Time_Out_Proc)(void *VMHandle, uint32_t tardiness, uint32_t ref_data);
+#pragma aux Global_Time_Out_Proc __parm [ebx] [ecx] [edx]
+
+static inline HTIMEOUT Set_Global_Time_Out(Global_Time_Out_Proc *proc, uint32_t ms, uint32_t refData);
+#pragma aux Set_Global_Time_Out = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 60" \
+ __parm caller [esi] __value [esi] __modify [ecx edx]
+
+static inline void Cancel_Time_Out(HTIMEOUT timeout);
+#pragma aux Cancel_Time_Out = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 62" \
+ __parm caller [esi] __modify [eax ecx edx]
+
+typedef void (Async_Time_Out_Proc)(uint32_t tardiness, uint32_t ref_data);
+#pragma aux Async_Time_Out_Proc __parm [ecx] [edx]
+
+static inline HTIMEOUT Set_Async_Time_Out(Async_Time_Out_Proc *proc, uint32_t ms, uint32_t refData);
+#pragma aux Set_Async_Time_Out = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 254" \
+ __parm caller [esi] [eax] [edx] __value [esi] __modify [ecx edx]
+
+static inline uint32_t Get_System_Time(void);
+#pragma aux Get_System_Time = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 63" \
+ __parm caller __value [eax] __modify []
+
+static inline uint32_t Get_Last_Updated_System_Time(void);
+#pragma aux Get_Last_Updated_System_Time = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 207" \
+ __parm caller __value [eax] __modify []
+
+static inline uint32_t * Get_System_Time_Address(void);
+#pragma aux Get_System_Time_Address = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 324" \
+ __parm caller __value [eax] __modify []
+
+enum {
+ HEAPZEROINIT = 0x0001,
+ HEAPZEROREINIT = 0x0002,
+ HEAPNOCOPY = 0x0004,
+ HEAPLOCKEDIFDP = 0x0100,
+ HEAPSWAP = 0x0200,
+ HEAPINIT = 0x0400,
+ HEAPCLEAN = 0x0800
+};
+
+static inline void * HeapAllocate(unsigned long bytes, unsigned long flags);
+#pragma aux HeapAllocate = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 79" \
+ __parm caller [] __value [eax] __modify [ecx edx]
+
+static inline unsigned long HeapFree(void *addr, unsigned long flags);
+#pragma aux HeapFree = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 81" \
+ __parm caller [] __value [eax] __modify [ecx edx]
+
+enum page_allocate_types {
+ PG_VM = 0,
+ PG_SYS = 1,
+ PG_RESERVED1 = 2,
+ PG_PRIVATE = 3,
+ PG_RESERVED2 = 4,
+ PG_RELOCK = 5,
+ PG_INSTANCE = 6,
+ PG_HOOKED = 7
+};
+
+enum page_allocate_flags {
+ PAGEZEROINIT = 0x00000001,
+ PAGEUSEALIGN = 0x00000002,
+ PAGECONTIG = 0x00000004,
+ PAGEFIXED = 0x00000008,
+ PAGEDEBUGNULFAULT = 0x00000010,
+ PAGEZEROREINIT = 0x00000020,
+ PAGENOCOPY = 0x00000040,
+ PAGELOCKED = 0x00000080,
+ PAGELOCKEDIFDP = 0x00000100,
+ PAGESETV86PAGEABLE = 0x00000200,
+ PAGECLEARV86PAGEABLE = 0x00000400,
+ PAGESETV86INTSLOCKED = 0x00000800,
+ PAGECLEARV86INTSLOCKED = 0x00001000,
+ PAGEMARKPAGEOUT = 0x00002000,
+ PAGEPDPSETBASE = 0x00004000,
+ PAGEPDPCLEARBASE = 0x00008000,
+ PAGEDISCARD = 0x00010000,
+ PAGEPDPQUERYDIRTY = 0x00020000,
+ PAGEMAPFREEPHYSREG = 0x00040000,
+ PAGENOMOVE = 0x10000000,
+ PAGEMAPGLOBAL = 0x40000000,
+ PAGEMARKDIRTY = 0x80000000
+};
+
+static inline void * PageAllocate(unsigned long npages, unsigned long type, unsigned long vm,
+ unsigned long alignMask, unsigned long minPhys, unsigned long maxPhys,
+ uint32_t *physAddr, unsigned long flags);
+#pragma aux PageAllocate = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 83" \
+ __parm caller [] __value [eax] __modify [ecx edx]
+
+static inline unsigned long PageFree(void *addr, unsigned long flags);
+#pragma aux PageFree = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 85" \
+ __parm caller [] __value [eax] __modify [ecx edx]
+
+static inline char Hook_Device_Service(uint32_t service, void *hook);
+#pragma aux Hook_Device_Service = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 144" \
+ "setc al" \
+ __parm caller [eax] [esi] __value [al] __modify [ecx edx]
+
+static inline char Unhook_Device_Service(uint32_t service, void *hook);
+#pragma aux Unhook_Device_Service = \
+ "int 0x20" \
+ "dd ( 1 shl 16 ) + 284" \
+ "setc al" \
+ __parm caller [eax] [esi] __value [al] __modify [ecx edx]
+
+#endif // VMM_H
diff --git a/w9x/inc/vxd.h b/w9x/inc/vxd.h
new file mode 100644
index 0000000..b73ab68
--- /dev/null
+++ b/w9x/inc/vxd.h
@@ -0,0 +1,132 @@
+#ifndef WINVXD_H
+#define WINVXD_H
+
+/** Some definitions from VMM or general VXD structs. */
+
+#include <stdint.h>
+
+#include <sassert.h>
+
+enum {
+ SDK_VERSION_3 = 0x30A,
+ SDK_VERSION_4 = 0x400
+};
+
+enum {
+ UNDEFINED_INIT_ORDER = 0x80000000UL
+};
+
+enum VXD_DEVICE_IDS {
+ UNDEFINED_DEVICE_ID = 0,
+ VMM_DEVICE_ID = 1,
+ VMD_DEVICE_ID = 0xC,
+ VKD_DEVICE_ID = 0xD,
+ CONFIGMG_DEVICE_ID = 0x33,
+};
+
+enum {
+ /* Required for w9x to load 4.0 VxD */
+ DDB_PREV_INIT = 'Prev'
+};
+
+typedef struct {
+ uint32_t next;
+ uint16_t sdk_version;
+ uint16_t req_device_number;
+ uint8_t dev_major_version;
+ uint8_t dev_minor_version;
+ uint16_t flags;
+ char name[8];
+ uint32_t init_order;
+
+ void * control_proc;
+ void * v86_api_proc;
+ void * pm_api_proc;
+ void * v86_api_csip;
+ void * pm_api_csip;
+ void * reference_data;
+ void * service_table_ptr;
+ uint32_t service_table_size;
+
+ /** Windows 9x */
+ void * win32_service_table;
+ uint32_t prev;
+ uint32_t size;
+
+ uint32_t reserved[3];
+} VxD_Desc_Block;
+STATIC_ASSERT(sizeof(VxD_Desc_Block) == 80);
+
+enum SYSTEM_CONTROL_MSGS {
+ SYS_CRITICAL_INIT = 0x0,
+ DEVICE_INIT = 0x1,
+ INIT_COMPLETE = 0x2,
+ SYS_VM_INIT = 0x3,
+ SYS_VM_TERMINATE = 0x4,
+ SYSTEM_EXIT = 0x5,
+ SYS_CRITICAL_EXIT=0x6,
+ CREATE_VM=0x7,
+ VM_CRITICAL_INIT=0x8,
+ VM_INIT=0x9,
+ VM_TERMINATE=0xA,
+ VM_NOT_EXECUTEABLE=0xB,
+ DESTROY_VM=0xC,
+ VM_SUSPEND=0xD,
+ VM_RESUME=0xE,
+ SET_DEVICE_FOCUS=0xF,
+
+ BEGIN_MESSAGE_MODE=0x10,
+ END_MESSAGE_MODE=0x11,
+
+ REBOOT_PROCESSOR=0x12,
+
+ QUERY_DESTROY=0x13,
+
+ DEBUG_QUERY=0x14,
+
+ BEGIN_PM_APP=0x15,
+ END_PM_APP=0x16,
+
+ DEVICE_REBOOT_NOTIFY=0x17,
+ CRIT_REBOOT_NOTIFY=0x18,
+
+ CLOSE_VM_NOTIFY=0x19,
+
+ POWER_EVENT=0x1A,
+
+ SYS_DYNAMIC_DEVICE_INIT=0x1B,
+ SYS_DYNAMIC_DEVICE_EXIT=0x1C,
+
+ CREATE_THREAD=0x1D,
+ THREAD_INIT=0x1E,
+ TERMINATE_THREAD=0x1F,
+ THREAD_NOT_EXECUTEABLE=0x20,
+ DESTROY_THREAD=0x21,
+
+ PNP_NEW_DEVNODE = 0x22,
+
+ W32_DEVICEIOCONTROL = 0x23,
+
+ SYS_VM_TERMINATE2 = 0x24,
+ //...
+ CLOSE_VM_NOTIFY2 = 0x2F,
+
+ KERNEL32_INITIALIZED=0x31,
+ KERNEL32_SHUTDOWN=0x32
+
+};
+
+static inline void clear_carry(void);
+#pragma aux clear_carry = "clc" modify exact [];
+
+static inline void set_carry(void);
+#pragma aux set_carry = "stc" modify exact [];
+
+static inline int set_carry_if_notzero(int arg);
+#pragma aux set_carry_if_notzero = \
+ "cmp eax, 1" \
+ parm [eax] \
+ value [eax] \
+ modify exact [];
+
+#endif // WINVXD_H
diff --git a/w9x/makefile b/w9x/makefile
new file mode 100644
index 0000000..115d6d2
--- /dev/null
+++ b/w9x/makefile
@@ -0,0 +1,40 @@
+# Debug Settings
+!ifeq debug 1
+# Dump debug log to IOS debug log
+debug_cflags = -DENABLE_DLOG=1
+!endif
+
+# Binaries to build
+bins = chudisk.pdr
+infs = chudisk.inf
+
+# Object files for chudisk.pdr
+pdr_objs = chupdr.obj
+
+vxd_cflags = -osi -s -zc -w3 -wcd=202 -i=../inc $(debug_cflags)
+# -osi to optimize for size, put intrinsics inline (to avoid runtime calls)
+# -s to disable stack checks, since it inserts calls to the runtime
+# -zc moves const data objects to text segment. required due to no data segment.
+# -w3 enables warnings
+# -wcd=202 disables the unreferenced function warning (e.g., for inline functions in headers)
+
+compile_vxd = *wcc386 -fo=$^@ $(vxd_cflags) $[@
+
+.BEFORE:
+ # We need DOS and Windows headers, not host platform's
+ set include=$(%watcom)/h/win;$(%watcom)/h
+
+all: $(bins) .SYMBOLIC
+
+chudisk.pdr: chudisk.lnk $(pdr_objs)
+ *wlink @$[@ name $@ file { $(pdr_objs) }
+
+chupdr.obj: chupdr.c .AUTODEPEND
+ $(compile_vxd)
+
+test.obj: test.asm .AUTODEPEND
+ wasm -fo=$^@ $[@
+
+clean: .SYMBOLIC
+ rm -f $(bins)
+ rm -f *.obj *.map
diff --git a/w9x/utils.h b/w9x/utils.h
new file mode 100644
index 0000000..527a14b
--- /dev/null
+++ b/w9x/utils.h
@@ -0,0 +1,33 @@
+/*
+ * CHU - Misc utilities
+ * Copyright (C) 2026 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/>.
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#define SWAPNC_32(w) (\
+ (((w) & 0x000000FFUL) << 24) |\
+ (((w) & 0x0000FF00UL) << 8) |\
+ (((w) & 0x00FF0000UL) >> 8) |\
+ (((w) & 0xFF000000UL) >> 24)\
+ )
+
+/** Computes log2 for an unsigned integer, uses 386+ bsr instruction */
+static inline unsigned ulog2(uint32_t x);
+#pragma aux ulog2 = "bsr eax, eax" parm [eax] value [eax] modify exact [] nomemory
+
+#endif // UTILS_H