diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | README.md | 146 | ||||
| -rw-r--r-- | dos/ch375dos.h | 103 | ||||
| -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/makefile | 66 | ||||
| -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.h | 7 | ||||
| -rw-r--r-- | inc/dlog.h (renamed from dlog.h) | 102 | ||||
| -rw-r--r-- | inc/sassert.h | 2 | ||||
| -rw-r--r-- | inc/scsi.h | 61 | ||||
| -rw-r--r-- | inc/usb.h (renamed from usb.h) | 6 | ||||
| -rw-r--r-- | linux/ch375lnx.h | 76 | ||||
| -rw-r--r-- | linux/chudisk.c | 622 | ||||
| -rw-r--r-- | linux/lnxabs.h | 139 | ||||
| -rw-r--r-- | linux/makefile | 28 | ||||
| -rwxr-xr-x | linux/mkdev.sh | 19 | ||||
| -rw-r--r-- | makefile | 94 | ||||
| -rw-r--r-- | w9x/ch375vxd.h | 74 | ||||
| -rw-r--r-- | w9x/chudisk.inf | 45 | ||||
| -rw-r--r-- | w9x/chudisk.lnk | 8 | ||||
| -rw-r--r-- | w9x/chupdr.c | 816 | ||||
| -rw-r--r-- | w9x/dumpdcb.h | 110 | ||||
| -rw-r--r-- | w9x/inc/configmg.h | 213 | ||||
| -rw-r--r-- | w9x/inc/ios.h | 1520 | ||||
| -rw-r--r-- | w9x/inc/vmm.h | 161 | ||||
| -rw-r--r-- | w9x/inc/vxd.h | 132 | ||||
| -rw-r--r-- | w9x/makefile | 40 | ||||
| -rw-r--r-- | w9x/utils.h | 33 |
42 files changed, 4729 insertions, 331 deletions
@@ -4,3 +4,4 @@ *.com *.sys *.map +*.pdr @@ -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: + + + +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. + + + +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. + + + +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/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/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 @@ -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; } @@ -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 */ @@ -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, §ors) != 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 @@ -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, §ors); + 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}* @@ -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 |
