From 6b8a368c51082fdd010ef5e1b0be4eb9b5059d1a Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 26 Oct 2026 00:50:06 +0100 Subject: refactor common parts into inc/ch375.h and add win9x, linux drivers --- .gitignore | 1 + README.md | 146 ++++- ch375.h | 423 --------------- chud.c | 418 --------------- chudinit.c | 221 -------- chudisk.c | 896 ------------------------------- chudisk.h | 90 ---- chudisk.lnk | 4 - cio.c | 147 ----- cio.h | 31 -- dlog.h | 359 ------------- dos/ch375dos.h | 103 ++++ dos/chud.c | 418 +++++++++++++++ dos/chudinit.c | 226 ++++++++ dos/chudisk.c | 895 +++++++++++++++++++++++++++++++ dos/chudisk.h | 93 ++++ dos/chudisk.lnk | 4 + dos/cio.c | 147 +++++ dos/cio.h | 31 ++ dos/inc/bda.h | 67 +++ dos/inc/bpb.h | 83 +++ dos/inc/dosapi.h | 182 +++++++ dos/inc/dosdrv.h | 319 +++++++++++ dos/inc/dosint.h | 250 +++++++++ dos/inc/mbr.h | 46 ++ dos/makefile | 66 +++ dos/utest.c | 500 +++++++++++++++++ dos/utils.h | 60 +++ inc/bda.h | 67 --- inc/bpb.h | 83 --- inc/ch375.h | 361 +++++++++++++ inc/chuver.h | 7 + inc/dlog.h | 439 +++++++++++++++ inc/dosapi.h | 182 ------- inc/dosdrv.h | 319 ----------- inc/dosint.h | 250 --------- inc/mbr.h | 46 -- inc/sassert.h | 2 - inc/scsi.h | 61 +++ inc/usb.h | 45 ++ linux/ch375lnx.h | 76 +++ linux/chudisk.c | 622 +++++++++++++++++++++ linux/lnxabs.h | 139 +++++ linux/makefile | 28 + linux/mkdev.sh | 19 + makefile | 94 ++-- test.c | 489 ----------------- usb.h | 47 -- utils.h | 60 --- w9x/ch375vxd.h | 74 +++ w9x/chudisk.inf | 45 ++ w9x/chudisk.lnk | 8 + w9x/chupdr.c | 816 ++++++++++++++++++++++++++++ w9x/dumpdcb.h | 110 ++++ w9x/inc/configmg.h | 213 ++++++++ w9x/inc/ios.h | 1520 ++++++++++++++++++++++++++++++++++++++++++++++++++++ w9x/inc/vmm.h | 161 ++++++ w9x/inc/vxd.h | 132 +++++ w9x/makefile | 40 ++ w9x/utils.h | 33 ++ 60 files changed, 8606 insertions(+), 4208 deletions(-) delete mode 100644 ch375.h delete mode 100644 chud.c delete mode 100644 chudinit.c delete mode 100644 chudisk.c delete mode 100644 chudisk.h delete mode 100644 chudisk.lnk delete mode 100644 cio.c delete mode 100644 cio.h delete mode 100644 dlog.h create mode 100644 dos/ch375dos.h create mode 100644 dos/chud.c create mode 100644 dos/chudinit.c create mode 100644 dos/chudisk.c create mode 100644 dos/chudisk.h create mode 100644 dos/chudisk.lnk create mode 100644 dos/cio.c create mode 100644 dos/cio.h create mode 100644 dos/inc/bda.h create mode 100644 dos/inc/bpb.h create mode 100644 dos/inc/dosapi.h create mode 100644 dos/inc/dosdrv.h create mode 100644 dos/inc/dosint.h create mode 100644 dos/inc/mbr.h create mode 100644 dos/makefile create mode 100644 dos/utest.c create mode 100644 dos/utils.h delete mode 100644 inc/bda.h delete mode 100644 inc/bpb.h create mode 100644 inc/ch375.h create mode 100644 inc/chuver.h create mode 100644 inc/dlog.h delete mode 100644 inc/dosapi.h delete mode 100644 inc/dosdrv.h delete mode 100644 inc/dosint.h delete mode 100644 inc/mbr.h create mode 100644 inc/scsi.h create mode 100644 inc/usb.h create mode 100644 linux/ch375lnx.h create mode 100644 linux/chudisk.c create mode 100644 linux/lnxabs.h create mode 100644 linux/makefile create mode 100755 linux/mkdev.sh delete mode 100644 test.c delete mode 100644 usb.h delete mode 100644 utils.h create mode 100644 w9x/ch375vxd.h create mode 100644 w9x/chudisk.inf create mode 100644 w9x/chudisk.lnk create mode 100644 w9x/chupdr.c create mode 100644 w9x/dumpdcb.h create mode 100644 w9x/inc/configmg.h create mode 100644 w9x/inc/ios.h create mode 100644 w9x/inc/vmm.h create mode 100644 w9x/inc/vxd.h create mode 100644 w9x/makefile create mode 100644 w9x/utils.h diff --git a/.gitignore b/.gitignore index e25cf3b..f42e1e9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ *.com *.sys *.map +*.pdr diff --git a/README.md b/README.md index 4cc5263..5c659ec 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,27 @@ -This is a DOS disk driver for the CH375 "USB ISA adapter", mostly designed with the Pocket386/Book386 in mind. +This is a disk driver the CH375 "USB ISA adapter", mostly designed with the Pocket386/Book386 in mind. +It allows you to use USB pendrives (and other mass storage devices, like USB HDDs) +as if they were removable disks. There is already a good number of drivers out there, but as far as I can see they are all derivatives of the original one, and I got tired of all the obscure hangs and crashes. This driver is in OpenWatcom C and written from scratch using the datasheets, so it has a (hopefully different) new repertoire of bugs to play with. +The same abstraction (ch375.h) is used to provide drivers for multiple OSes +that work on the Pocket386/Book386: DOS, Win9x, and early Linux (~ 2.0/2.2). -Some features: +Hot plug/unplug is also supported, +but it will only be noticed the next time you access the drive. + +# Download + +Current version is 1.0 [📦 chu1_0.zip](https://depot.javispedro.com/dos/chu/chu1_0.zip). + +[Older versions are available here.](https://depot.javispedro.com/dos/chu/) + +[TOC] + +# DOS (MS-DOS, FreeDOS, etc.) + +Features on DOS platforms: * *Can support multiple partitions.* You can either show multiple drive letters (the number of drive letters is fixed at boot, though) and/or dynamically switch a single drive letter to a different partition from the command prompt. @@ -20,7 +37,7 @@ If DOS asks for geometry, it gives whatever is on the BPB of your current partit (only to re-format existing partitions, since it will simply re-use geometry from whatever FAT is already there). Note this is a DOS-level driver, so FDISK won't work (which expects a BIOS/int13-level driver). -* Build is for 386+, tested on pocket386/book386, but source builds on 8086. +* Build is for 386+, tested on pocket386/book386, but the source builds on 8086 at least. * Extensive (not included by default) verbose log over serial port. @@ -29,21 +46,13 @@ any benchmark in speed or memory usage, but it gets around. About 400KB/s on the I get with other drivers anyway; and around 7KiB of memory usage (the official driver sits at 4KiB). Hopefully much easier to experiment with than with the official one. -# Download - -[chu.zip](https://depot.javispedro.com/dos/chu/chu0.zip) -Current version is 0. - - -# Installation +## Installation ```` DEVICE=CHUDISK.SYS [options] ```` -## CHUDISK.SYS configuration options - Current options: * /P260 : IO port for CH375 (defaults to 0x260, as on the pocket386) @@ -56,7 +65,7 @@ Each drive letter will by default be mapped to a different partition in order. * /3 : show FAT32 partitions to the OS. Useful for OSes with native FAT32 support like FreeDOS. -# Usage +## Usage The program CHUD.EXE can be used to switch a CHUDISK.SYS managed drive letter to a different partition. E.g. @@ -71,16 +80,121 @@ C:\> CHUD D: Select partition in [1..3] range: ```` -Extended partitions, W95/LBA partitions are supported. Of course you can only select FAT16 partitions, -and perhaps FAT32 ones if lucky (DOS7.1, FreeDOS, etc.) or forced with /3. +Extended partitions, W95/LBA partitions are supported. +Only FAT partitions can be selected. +FAT32 partitions can also be selected on a DOS that supports them (DOS7.1, FreeDOS, etc.). Note the drive letters <-> partitions mapping is not persistent, and will in fact be reset if you reboot or disconnect the USB drive. -# Building +## Building Tested with OpenWatcom 2, build from any OS where OpenWatcom runs. ```` wmake all ```` + +If you add `debug=1`, then it will build debug versions which pass +all dlog/dprintf statements to the first serial port (but they are very slow). + +# Windows 9x + +On Windows 9x, the driver is a IOS port driver, which makes it appear as +a removable disk. Windows takes cares of parsing partition tables here. + +Note that because it is marked as removable, Windows disables caching, +so launching stuff from the USB disk will be very slow. + +By default, Windows *assigns 1 drive letter to removable drives*, but if the +drive is inserted before Windows starts, it will instead assign as many +letters as you have partitions. +You can also force Windows to assign more than one drive letter, or even +decide which drive letters you prefer for the partitions on your pendrive. +For that, go to Device Manager, Disk Drives, and select the CH375 USB Disk +as follows: + +![Assigning Disk Drive Leters](https://depot.javispedro.com/dos/chu/config.png) + +Partitions will be assigned to disk letters in order. + +## Installation + +You have to follow the usual procedure for non-PnP hardware in Win 9x. + +1. Go to the Add New Hardware Wizard in Control Panel. + +2. Say you do not want to do automatic detection, and select "Hard Disk Controller" +from the type of devices. + +![Add New Hardware Wizard](https://depot.javispedro.com/dos/chu/install1.png) + +3. Use "Have Disk..." and point to the directory containing `chudisk.pdr` and +`chudisk.inf`. + +4. When asked, choose "CH375 USB Disk Controller" + +5. At this point Windows creates the device with some random default I/O port +settings. Do not reboot yet. + +6. Go to Device Manager, choose the CH375 Disk Controller device (not the disk). +In the Resources tab, you can configure the IO address to match whatever your +hardware is using. On the Pocket386/Book386, this is 0x260-0x261. + +![Resources tab](https://depot.javispedro.com/dos/chu/install2.png) + +7. Windows will tell you that now you can shutdown the computer to install +your hardware. It's already installed, so reboot. + +## Building + +Uses OpenWatcom, same as DOS one. With `debug=1` , by default the debug log +goes into the IOS debug log, which will only be shown in the debug versions +of IOS. + +# Linux 2.0-2.2 + +No binaries are currently provided for Linux, since I find it easier to just +build for your distribution. You can even do that on-device. + +I have tested this on Debian Potato, with the latest 2.0 and 2.2 kernels. +Likely won't build on any other kernel version. + +## Installation / building + +Ensure that ``/lib/modules/`uname -r`/build`` exists, and that it points +to the kernel headers. +For Debian, this is usually a `kernel-headers-nnnn` package. +Note you cannot put a generic kernel source dump there, as it will lack the +usual headers generated during kernel build. + +```` +$ make +(as root:) +# make install +# ./mkdev.sh +(this will generate all /dev/chud* device files) +```` + +You can then use/mount the `/dev/chuda1`, `/dev/chuda2` devices as usual. + +## Usage + +The driver exposes the following parameters: + +* `io` the io address for your CH375, defaults to 0x260. +* `major` allows overriding the device's major number, in the rare case you +have a conflict. If you do this, also update `mkdev.sh`. +* `force` will load the driver even if we can't find a CH375 at the specified +address. +* `softblk` allows setting the "block size" used for transfers. +Linux defaults to 1024. Higher values provide higher speed at the cost +of latency. Must be power of two. + + +# Future work + +* Currently the driver is polling AND blocking in all OSes. Due to the +relative fast speed of most pendrives, the driver spends most of its time +doing PIO rather than waiting for the CH375, so I do not see the reason +to change this. Also, on Pocket386/Book386, the IRQ line is not wired. diff --git a/ch375.h b/ch375.h deleted file mode 100644 index a1ff1b0..0000000 --- a/ch375.h +++ /dev/null @@ -1,423 +0,0 @@ -/* - * CH375 hardware header - * Copyright (C) 2024 Javier S. Pedro - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -#ifndef CH375_H -#define CH375_H - -#include -#include -#include -#include - -#include "usb.h" - -extern unsigned short ch375_port; - -enum { - CH375_PORT_DATA = 0, - CH375_PORT_CMD = 1, -}; - -enum ch375_command { - /** Returns chip and firmware version in bit 5-0. - * output: 1byte. - * C375B is 0x37. */ - CH375_GET_IC_VERSION = 0x01, - /** (Host mode) Set serial port baud rate. */ - CH375_SET_BAUDRATE = 0x02, - /** Enter low power mode. Wakes up with USB or host activity. */ - CH375_ENTER_SLEEP = 0x03, - /** Manualy force USB speed. - * Input: 1 byte: - * 0: Full speed - * 1: Full speed with low speed clock frequency (??) - * 2: Low speed */ - CH375_SET_USB_SPEED = 0x04, - /** Hardware reset. May take up to 40ms. */ - CH375_RESET = 0x05, - /** Simple eco function. Returns input byte negated. - * Input: 1 byte: x - * Output: 1 byte: ~x */ - CH375_CHECK_EXIST = 0x06, - - CH375_READ_REG = 0x0A, - CH375_WRITE_REG = 0x0B, - /** Sets a 100us timer. - * Output: infinite bytes. 0 while timer not expired, non-0 when expired. */ - CH375_DELAY_100US = 0x0F, - /** (Device mode) set our USB vid and PID. - * Input: 4 bytes: VID LSB, VID MSB, PID LSB, PID MSB */ - CH375_SET_USB_ID = 0x12, - /** (Device mode) set USB address. ?? */ - CH375_SET_USB_ADDR = 0x13, - /** Set USB mode. Usually takes 20µs. - * Input: 1 byte: - * 0 : Device mode disabled (i.e. device plug-in detection) - * 1 : Device mode with external firmware - * 2 : Device mode with built-in firmware - * 4 : Host mode disabled (i.e. device plug-in detection) - * 5 : Host mode without automatic SOF packets. - * 6 : Host mode with automatic SOF packets. - * 7 : Host mode, initiate bus reset - * Output: 1 byte: - * CMD_RET_SUCCESS if OK - * CMD_RET_ABORT, etc. if error - */ - CH375_SET_USB_MODE = 0x15, - /** (Host mode) Check the connection status. - * Output: 1 byte: either USB_INT_CONNECT or USB_INT_DISCONNECT or USB_INT_USB_READY. */ - CH375_TEST_CONNECT = 0x16, - /** Abandon retrying and return NAK. */ - CH375_ABORT_NAK = 0x17, - - CH375_SET_ENDP2 = 0x18, - CH375_SET_ENDP3 = 0x19, - CH375_SET_ENDP4 = 0x1A, - CH375_SET_ENDP5 = 0x1B, - CH375_SET_ENDP6 = 0x1C, - CH375_SET_ENDP7 = 0x1D, - - /** Get interrupt status and ack pending interrupts. - * Output: 1 byte */ - CH375_GET_INT_STATUS = 0x22, - /** (Device mode) Release current USB buffer. */ - CH375_UNLOCK_USB = 0x23, - /** Read buffer of endpoint of current interrupt. - * Output: length byte + data. */ - CH375_RD_USB_DATA0 = 0x27, - /** Equivalent to RD_USB_DATA0 + UNLOCK_USB. */ - CH375_RD_USB_DATA = 0x28, - CH375_WR_USB_DATA3 = 0x29, - CH375_WR_USB_DATA5 = 0x2A, - CH375_WR_USB_DATA7 = 0x2B, - /** (Host mode) Clear error state on endpoint. - * Input: 1 byte: endpoint number. - * On done: Triggers interrupt. */ - CH375_CLR_STALL = 0x41, - /** (Host mode) Set USB address. - * Input: USB address. - * On done: Triggers interrupt. */ - CH375_SET_ADDRESS = 0x45, - CH375_GET_DESCR = 0x46, - CH375_SET_CONFIG = 0x49, - CH375_AUTO_SETUP = 0x4D, - CH375_ISSUE_TKN_X = 0x4E, - CH375_ISSUE_TOKEN = 0x4F, - - CH375_DISK_BOC = 0x50, - CH375_DISK_INIT = 0x51, - CH375_DISK_RESET = 0x52, - CH375_DISK_SIZE = 0x53, - CH375_DISK_READ = 0x54, - CH375_DISK_RD_GO = 0x55, - CH375_DISK_WRITE = 0x56, - CH375_DISK_WR_GO = 0x57, - CH375_DISK_INQUIRY = 0x58, - CH375_DISK_READY = 0x59, - CH375_DISK_R_SENSE = 0x5A, -}; - -enum ch375_cmd_ret { - CH375_RET_SUCESS = 0x51, - CH375_RET_ABORT = 0x5F -}; - -enum ch375_int { - /** if (x & mask) == val, this is a USB device mode interrupt. */ - CH375_INT_DEVICE_MASK = 0xF0, - CH375_INT_DEVICE_VAL = 0x00, - - CH375_INT_HOST_MASK = 0xE0, - CH375_INT_HOST_VAL = 0x20, - - CH375_INT_BUS_RESET1 = 0x03, - CH375_INT_BUS_RESET2 = 0x07, - CH375_INT_BUS_RESET3 = 0x0B, - CH375_INT_BUS_RESET4 = 0x0F, - - /** USB bus suspend event. */ - CH375_INT_USB_SUSPEND = 0x05, - /** Wakeup from USB suspend. */ - CH375_INT_USB_WAKE_UP = 0x06, - CH375_INT_USB_SUCCESS = 0x14, - CH375_INT_USB_CONNECT = 0x15, - CH375_INT_USB_DISCONNECT = 0x16, - CH375_INT_USB_BUF_OVER = 0x17, - CH375_INT_USB_READY = 0x18, - CH375_INT_USB_DISK_READ = 0x1D, - CH375_INT_USB_DISK_WRITE = 0x1E, - 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.*/ - CH375_REG_DEV_RATE = 0x0A, - /** (Device mode): how to check USB bus suspend status. - * 0: Do not check - * 4: Check at 50ms intervals - * 5: Check at 10ms intervals */ - CH375_REG_CHK_SUSPEND = 0x10, - CH375_REG_TOGGLE = 0x1A, - - /** ??? - * Bit 5 : set after disk is online? but never unset... */ - CH375_REG_DISK_STATUS = 0x20, - /** (Host mode) Set the number of retries for USB transactions. - * If bit 7 is 0, no retry is performed when NAK is received. - * If bit 7 is 1 and bit 6 is 0, unlimited retries are performed when NAK is received. - * If bit 7 is 1 and bit 6 is 1, the maximum retry is 2 seconds when NAK is received. - * Bits 5-0 are the number of retries after timeout. */ - CH375_REG_RETRY = 0x25, - /** (Host disk mode) Set the current LUN. */ - CH375_REG_DISK_LUN = 0x34, - CH375_REG_DISK_MAX_LUN = 0x38, - /** (Host disk mode) Set the total number of 64-byte packets per sector. - * E.g. 8 = 512 byte sectors. */ - CH375_REG_DISK_PKT_P_SEC = 0x39, - /// Drive letter (in ASCII) currently assigned. This is set by software. - CH375_REG_DISK_NAME = 0x3E - - // 0x1B what's on this register ? 0x7F , 0x80 values seen - // 0x20 ?? some type of status ? - // 0x31, 0x32, 0x33 ?? - -}; - -static uint8_t ch375_read_reg(enum CH375_REG reg) -{ - ch375_send_command(CH375_READ_REG); - ch375_write_data(reg); - return ch375_read_data(); -} - -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); -} - -/** 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(); - - for (i = 0; i < n; i++) { - buffer[i] = ch375_read_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; - - ch375_write_data(n); - - for (i = 0; i < n; i++) { - ch375_write_data(buffer[i]); - } -} -#endif - -// GLOBAL COMMANDS - -static inline uint8_t ch375_get_version(void) -{ - ch375_send_command(CH375_GET_IC_VERSION); - return ch375_read_data() & 0x3F; -} - -static inline void ch375_enter_sleep(void) -{ - ch375_send_command(CH375_ENTER_SLEEP); -} - -static inline void ch375_reset(void) -{ - ch375_send_command(CH375_RESET); -} - -static inline uint8_t ch375_check_exists(uint8_t val) -{ - ch375_send_command(CH375_CHECK_EXIST); - ch375_write_data(val); - return ch375_read_data(); -} - -static inline uint8_t ch375_get_int_status(void) -{ - ch375_send_command(CH375_GET_INT_STATUS); - return ch375_read_data(); -} - -enum ch375_usb_mode { - CH375_USB_MODE_DEVICE_DISABLED = 0, - CH375_USB_MODE_DEVICE_EXTERNAL_FIRMWARE = 1, - CH375_USB_MODE_DEVICE_INTERNAL_FIRMWARE = 2, - CH375_USB_MODE_HOST_DISABLED = 4, - CH375_USB_MODE_HOST = 5, - CH375_USB_MODE_HOST_AUTO_SOF = 6, - 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); -} - -enum ch375_usb_speed { - CH375_USB_SPEED_FULL = 0, - CH375_USB_SPEED_LOW = 2 -}; - -// 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 ch375a_set_low_speed(void) { - // Undocumented. - ch375_write_reg(0x17, 0xd8); -} - -// HOST COMMANDS - -static void ch375_test_connect(void) { - ch375_send_command(CH375_TEST_CONNECT); -} - -static void ch375_auto_setup(void) { - ch375_send_command(CH375_AUTO_SETUP); -} - -enum ch375_retry_mode { - CH375_RETRY_NAK_INFINITE = 0x80, - CH375_RETRY_NAK_2S = 0xC0, - CH375_RETRY_NAK_NONE = 0x00, - - 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_get_descriptor(usb_descriptor_type type) { - ch375_send_command(CH375_GET_DESCR); - ch375_write_data(type); -} - -// DISK COMMANDS - -enum ch375_reg_disk_status { - CH375_REG_DISK_STATUS_READY = 0x20 -}; - -static inline bool ch375_is_disk_ready(void) -{ - return ch375_read_reg(CH375_REG_DISK_STATUS) & CH375_REG_DISK_STATUS_READY; -} - -static void ch375_disk_init() -{ - ch375_send_command(CH375_DISK_INIT); -} - -static void ch375_disk_read(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); -} - -static void ch375_disk_write(uint32_t lba, uint8_t nsectors) -{ - 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); -} - -static void ch375_test_disk_ready(void) -{ - ch375_send_command(CH375_DISK_READY); -} - -#endif diff --git a/chud.c b/chud.c deleted file mode 100644 index c6d728c..0000000 --- a/chud.c +++ /dev/null @@ -1,418 +0,0 @@ -/* - * CHU - driver for CH375 in disk mode - * Copyright (C) 2024 Javier S. Pedro - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "inc/mbr.h" -#include "inc/dosdrv.h" - -#include "chudisk.h" - -uint16_t ch375_port; - -bool read_only; -bool show_fat32; - -uint8_t drive; -uint32_t partition_start; -uint32_t partition_len; - -#define MAX_PARTITIONS 32 -struct partinfo { - uint32_t start_sector; - uint32_t num_sectors; - uint8_t type; -} partinfo[MAX_PARTITIONS]; -unsigned int num_parts; - -static int ioctl_read_control(uint8_t drive, void *buffer, int len) -{ - union REGS regs; - regs.h.ah = DOS_IOCTL; - regs.h.al = 0x04; // Read control string from block device - regs.x.bx = drive; - regs.x.cx = len; - regs.x.dx = FP_OFF(buffer); - - intdos(®s, ®s); - - if (regs.x.cflag) { - return -1; - } else { - return regs.x.ax; - } -} - -static int ioctl_write_control(uint8_t drive, void *buffer, int len) -{ - union REGS regs; - regs.h.ah = DOS_IOCTL; - regs.h.al = 0x05; // Write control string to block device - regs.x.bx = drive; - regs.x.cx = len; - regs.x.dx = FP_OFF(buffer); - - intdos(®s, ®s); - - if (regs.x.cflag) { - return -1; - } else { - return regs.x.ax; - } -} - -static int ioctl_read_abs_sector(uint8_t drive, void *buffer, uint32_t sector, int sectors) -{ - CHUDISK_IOCTL_DISK_DATA ciod; - union REGS regs; - - ciod.rwabs.buffer = buffer; - ciod.rwabs.sector = sector; - ciod.rwabs.sectors = sectors; - - regs.h.ah = DOS_IOCTL; - regs.h.al = 0x0D; // IOCTL for block devices - regs.x.bx = drive; - regs.h.ch = DOS_IOCTL_DISK; - regs.h.cl = CHUDISK_IOCTL_DISK_READ_ABS_SECTOR; - regs.x.dx = FP_OFF(&ciod); - - intdos(®s, ®s); - - return regs.x.cflag ? -1 : 0; -} - -static void dos_disk_reset() -{ - union REGS regs; - regs.h.ah = 0x0D; // DOS DISK RESET - - intdos(®s, ®s); -} - -static int parse_chudisk_control_string(const char *b, int len) -{ - // Not sure why I'm doing it this way instead of just sharing a pointer. - int i = 0; -#if 0 - printf("control string:\n"); - for (i = 0; i < len; i+=8) { - printf(" %hx %hx %hx %hx %hx %hx %hx %hx\n", - b[i+0], b[i+1], b[i+2], b[i+3], b[i+4], b[i+5], b[i+6], b[i+7]); - } - i = 0; -#endif - while (i < len) { - char c = b[i++]; - switch (c) { - case 'B': - memcpy(&ch375_port, &b[i], sizeof(ch375_port)); - i+=sizeof(ch375_port); - break; - case '3': - show_fat32 = b[i++]; - break; - case 'R': - read_only = b[i++]; - break; - case 'S': - memcpy(&partition_start, &b[i], sizeof(partition_start)); - i+=sizeof(partition_start); - break; - case 'L': - memcpy(&partition_len, &b[i], sizeof(partition_len)); - i+=sizeof(partition_len); - break; - case '\0': - return 0; - default: - fprintf(stderr, "Unidentified control string parameter: '%c'\n", c); - return -1; - } - } - - return 0; -} - -static bool is_selectable_partition(const MBR_PARTENTRY *partentry) -{ - return partentry->type == MBR_FAT12 || partentry->type == MBR_FAT16_32MB - || partentry->type == MBR_FAT16 || partentry->type == MBR_FAT16_LBA - || (show_fat32 && (partentry->type == MBR_FAT32 || partentry->type == MBR_FAT32_LBA)); -} - -static const char *get_partition_type(const MBR_PARTENTRY *partentry) -{ - static char buffer[8]; - switch (partentry->type) { - case MBR_EMPTY: - return "invalid"; - case MBR_FAT12: - return "fat12"; - case MBR_FAT16_32MB: - case MBR_FAT16: - case MBR_FAT16_LBA: - return "fat16"; - case MBR_IFS: - return "ntfs"; - case MBR_FAT32: - case MBR_FAT32_LBA: - return "fat32"; - case MBR_EXT: - case MBR_EXT_LBA: - return "extended"; - case MBR_LINUX: - case MBR_LINUX_SWAP: - return "linux"; - default: - sprintf(buffer, "%#X", partentry->type); - return buffer; - } -} - - -static const char * show_partition_size(const MBR_PARTENTRY *partentry) -{ - static char buffer[12]; - uint32_t size = partentry->lbaLen / 2; - STATIC_ASSERT(SECTOR_SIZE == 512); - - if (size <= 10000) { - sprintf(buffer, "%lu KB", size); - return buffer; - } - - size /= 1024; - if (size <= 10000) { - sprintf(buffer, "%lu MB", size); - return buffer; - } - - size /= 1024; - - sprintf(buffer, "%lu GB", size); - return buffer; -} - -static const char *get_partition_label(const MBR_PARTENTRY *partentry, const uint32_t start_sector) -{ - static char buffer[SECTOR_SIZE]; - DOS_BPB *bpb = (DOS_BPB*) &buffer[DOS_BPB_OFFSET_ON_DISK]; - - switch (partentry->type) { - // Focus on well-known types for now - case MBR_FAT12: - case MBR_FAT16_32MB: - case MBR_FAT16: - case MBR_FAT16_LBA: - case MBR_FAT32: - case MBR_FAT32_LBA: - break; - default: - return " "; - } - - if (ioctl_read_abs_sector(drive, buffer, start_sector, 1) != 0) { - fprintf(stderr, "while reading partition boot record at %lu: %s\n", start_sector, strerror(errno)); - return " ???? "; - } - - if (bpb->sectorsPerFAT != 0) { - // Assume FAT16 - return bpb->fat16.volumeLabel; - } else { - // Assume FAT32 - return bpb->fat32.volumeLabel; - } -} - -static void print_partition(const uint32_t mbr_sector, const MBR_PARTENTRY *partentry, bool selectable) -{ - const bool ebr = mbr_sector != 0; - const uint32_t start_sector = mbr_sector + partentry->lbaFirst; - const bool is_current = partition_start == start_sector; - const char *typeLabel = get_partition_type(partentry); - const char *volumeLabel = get_partition_label(partentry, start_sector); - const char *sizeLabel = show_partition_size(partentry); - - if (partentry->type == MBR_EMPTY) { - // Skip empty partitions no matter what - return; - } - if (ebr && (partentry->type == MBR_EXT || partentry->type == MBR_EXT_LBA)) { - // Skip displaying EXT partitions EBR, noisy - return; - } - - if (selectable) { - const unsigned index = num_parts + 1; - - printf(" %c[%2u] %8s %-11.11s %10s \n", - is_current ? '*' : ' ', index, typeLabel, volumeLabel, sizeLabel); - } else { - printf(" %c[ ] %8s %-11.11s %10s \n", - is_current ? '*' : ' ', typeLabel, volumeLabel, sizeLabel); - } - -} - -static int scan_disk_partitions() -{ - static char buffer[SECTOR_SIZE]; - uint32_t next_sector = 0; - - printf(" Index %8s %-11.11s %10s \n", - "Type", "Label", "Size"); - - // Start with MBR at sector 0 - next_sector = 0; - do { - const uint32_t this_sector = next_sector; - const MBR_PARTENTRY *partentry; - int i; - - // Try to read sector - if (ioctl_read_abs_sector(drive, buffer, this_sector, 1) != 0) { - fprintf(stderr, "while reading %s at disk sector %lu: %s\n", - this_sector == 0 ? "MBR" : "EBR", - this_sector, strerror(errno)); - // Fatal for MBR, non-fatal for EBR - if (this_sector == 0) { - fprintf(stderr, "is the disk present?\n"); - return -1; - } - break; - } - - // Check for signature - if (*(uint16_t*)&buffer[MBR_SIGNATURE_OFFSET_ON_DISK] != 0xAA55U) { - fprintf(stderr, "on disk sector %lu: invalid MBR signature %x\n", - this_sector, *(uint16_t*)&buffer[MBR_SIGNATURE_OFFSET_ON_DISK]); - if (this_sector == 0) return -1; - break; - } - - // Parse the MBR - partentry = (MBR_PARTENTRY*)&buffer[MBR_PARTENTRY_OFFSET_ON_DISK]; - - next_sector = 0; // If there is another EBR we will store it here - for (i = 0; i < 4 && num_parts < MAX_PARTITIONS; i++) { - bool is_selectable = is_selectable_partition(&partentry[i]); - - print_partition(this_sector, &partentry[i], is_selectable); - - if (is_selectable) { - partinfo[num_parts].type = partentry[i].type; - partinfo[num_parts].start_sector = this_sector + partentry[i].lbaFirst; - partinfo[num_parts].num_sectors = partentry[i].lbaLen; - num_parts++; - } - if (partentry[i].type == MBR_EXT || partentry[i].type == MBR_EXT_LBA) { - // Go to the EBR next - next_sector = this_sector + partentry[i].lbaFirst; - } - } - } while (next_sector != 0 && num_parts < MAX_PARTITIONS); - - return 0; -} - -static void usage() -{ - printf("Usage: chud X:, where X is a drive letter handled by CHUDISK.SYS\n"); -} - -int main(int argc, const char *argv[]) -{ - static char buffer[16]; - int i; - unsigned part; - - for (i = 1; i < argc; i++) { - // Drive letter? - if (argv[i][0] >= 'A' && argv[i][0] <= 'Z' && argv[i][1] == ':') { - drive = 1 + argv[i][0] - 'A'; - } else if (argv[i][0] >= 'a' && argv[i][0] <= 'z' && argv[i][1] == ':') { - drive = 1 + argv[i][0] - 'a'; - } - } - - // Now read the driver settings and current partition - i = ioctl_read_control(drive, buffer, sizeof(buffer)); - if (i < 0) { - perror("ioctl_read_control"); - usage(); - return EXIT_FAILURE; - } - - if (parse_chudisk_control_string(buffer, i)) { - fprintf(stderr, "wrong IOCTL string returned by driver\n"); - usage(); - return EXIT_FAILURE; - } - - if (scan_disk_partitions() != 0) { - return EXIT_FAILURE; - } - if (num_parts == 0) { - fprintf(stderr, "No partitions found on the disk\n"); - return EXIT_FAILURE; - } - - printf("Select partition in [1..%u] range: ", num_parts); - i = scanf("%u", &part); - - if (i != 1) { - // Assume cancel - return EXIT_FAILURE; - } - - if (part == 0 || part > num_parts) { - fprintf(stderr, "Invalid partition %u\n", part); - return EXIT_FAILURE; - } - - printf("Switching drive to partition %u at offset %lu size %lu\n", part, - partinfo[part-1].start_sector, partinfo[part-1].num_sectors); - - dos_disk_reset(); // Flush caches before we swap the drive... - - i = 0; - buffer[i++] = 'S'; - memcpy(&buffer[i], &partinfo[part-1].start_sector, sizeof(uint32_t)); - i+=sizeof(uint32_t); - buffer[i++] = 'L'; - memcpy(&buffer[i], &partinfo[part-1].num_sectors, sizeof(uint32_t)); - i+=sizeof(uint32_t); - buffer[i] = '\0'; - - if (ioctl_write_control(drive, buffer, i) < i) { - perror("ioctl_write_control"); - return EXIT_FAILURE; - } - - // The above IOCTL will also cause the next mediacheck from DOS to return changed. - - return EXIT_SUCCESS; -} diff --git a/chudinit.c b/chudinit.c deleted file mode 100644 index 1919100..0000000 --- a/chudinit.c +++ /dev/null @@ -1,221 +0,0 @@ -/* - * CHU - driver for CH375 in disk mode - * Copyright (C) 2024 Javier S. Pedro - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -#include -#include - -#include "inc/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" - -static int parse_int(const char far * far *s) -{ - int i = 0; - while (**s >= '0' && **s <= '9') { - i = (i * 10) + (**s - '0'); - (*s)++; - } - if (**s == ' ' || **s == '\r' || **s == '\n') { - return i; - } else { - return -1; - } -} - -static int parse_hex(const char far * far *s) -{ - int i = 0; - while ((**s >= '0' && **s <= '9') || (**s >= 'A' && **s <= 'F') || (**s >= 'a' && **s <= 'f')) { - if (**s >= '0' && **s <= '9') i = (i * 16) + (**s - '0'); - else if (**s >= 'A' && **s <= 'F') i = (i * 16) + (**s - 'A'); - else if (**s >= 'a' && **s <= 'f') i = (i * 16) + (**s - 'a'); - (*s)++; - } - if (**s == ' ' || **s == '\r' || **s == '\n') { - return i; - } else { - return -1; - } -} - -static bool parse_cmdline(const char far *cmdline) -{ - // Source command line string - // E.g. DEVICE.SYS /N3\r\n - const char far *s = cmdline; - - // Skip filename part (search for first sep) - while (*s != ' ' && *s != '\r' && *s != '\n') { s++; } - if (*s == '\r' || *s == '\n') return true; // No arguments - // Now skip whitespace - while (*s == ' ') { s++; } - - // Now handle options like /N3 /X4 - while (*s != '\r' && *s != '\n') { - char c = *s++, opt; - int num; - switch (c) { - case '/': - opt = *s++; - switch (opt) { - case 'N': - case 'n': // number of units - num = parse_int(&s); - if (num > 0 && num <= MAX_UNITS) { - num_units = num; - } else { - c_printf("invalid value '%d' for option '%c'\n", num, opt); - return false; - } - break; - case 'P': - case 'p': // IO port - num = parse_hex(&s); - if (num > 0) { - ch375_port = num; - } else { - c_printf("invalid value '%d' for option '%c'\n", num, opt); - return false; - } - break; - case 'R': - case 'r': // Read only - read_only = true; - break; - case '3': // Show FAT32 partitions - show_fat32 = true; - break; - default: - c_printf("invalid option '%c'\n", opt); - return false; - } - break; - case ' ': - break; - default: - c_printf("invalid syntax '%c'\n", c); - return false; - } - } - - dprintf("Configured num drives: %hu\n", num_units); - - return true; -} - -static void fill_initial_bpb(DOS_BPB *bpb) -{ - // Dummy, only used during driver init - // Most likely these values can be anything you want, - // as long as it remotely looks like a valid FAT16 BPB - bpb->bytesPerSector = 512; - bpb->sectorsPerCluster = 1; - bpb->reservedSectors = 0; - bpb->numberOfFATs = 2; - bpb->rootDirEntries = 16; - bpb->numberOfSectors = 512; - bpb->mediaType = DOS_BPB_MEDIA_HDD; - bpb->sectorsPerFAT = 1; - bpb->sectorsPerTrack = 63; - bpb->numberOfHeads = 255; - bpb->hiddenSectors = 0; - bpb->numberOfSectorsLarge = 0; - memcpy(bpb->fat16.volumeLabel, "NO NAME ", 11); -} - -void driver_init(DOS_DRS __far *drs) -{ - unsigned i; - uint16_t dos_version; - uint8_t dos_major, dos_minor; - uint8_t hw_version; - - dlog_init(); - - // Defaults - ch375_port = 0x260; - num_units = 1; - read_only = false; - show_fat32 = false; - - // If DOS > 7.1, enable FAT32 by default - dos_version = dos_get_version(); - dos_major = dos_version & 0xFF; - dos_minor = (dos_version >> 8) & 0xFF; - dprintf("DOS version %d.%d\n", dos_major, dos_minor); - if (dos_major > 7 || (dos_major == 7 && dos_minor >= 10)) { - show_fat32 = true; - } - - // Parse command line arguments - if (!parse_cmdline(drs->cmd.init.in.cmdline)) { - c_puts("CHUDISK: Syntax error in cmdline arguments"); - goto fail; - } - - // Try to initialize CH375 - dputs("CH375 reset..."); - ch375_reset(); - 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); - goto fail; - } - - hw_version = ch375_get_version(); - - // Do not really set up the chip until it is used - ch375_set_usb_mode(CH375_USB_MODE_HOST_DISABLED); - ch375_enter_sleep(); - - data.ready = false; - data.sectors = 0; - data.bytesPerSector = 0; - - // Build the template BPBs - drs->cmd.init.out.number_units = num_units; - for (i = 0; i < num_units; i++) { - fill_initial_bpb(&data.unit[i].bpb); - data.bpb_ptrs[i] = &data.unit[i].bpb; - } - drs->cmd.init.out.bpbs = data.bpb_ptrs; - 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); - for (i = 0; i < num_units; i++) { - c_printf(" %c:", drive_index_to_letter(drs->cmd.init.in.first_drive_number + i)); - } - c_putc('\n'); - - return; -fail: - drs->status = DOS_DD_STATUS_DONE | DOS_DD_STATUS_ERROR | DOS_DD_ERR_GENERAL_FAILURE; - drs->cmd.init.out.number_units = 0; - drs->cmd.init.out.break_addr = MK_FP(get_cs(), 0); // Deallocate everything - return; -} diff --git a/chudisk.c b/chudisk.c deleted file mode 100644 index 24de25e..0000000 --- a/chudisk.c +++ /dev/null @@ -1,896 +0,0 @@ -/* - * CHU - driver for CH375 in disk mode - * Copyright (C) 2024 Javier S. Pedro - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -#include -#include - -#include "inc/dosdrv.h" -#include "inc/bda.h" -#include "inc/mbr.h" - -#include "chudisk.h" -#include "ch375.h" -#include "dlog.h" -#include "utils.h" - -// Pre-declarations for the two driver routines, note there is a pragma aux for them -DOS_DD_STRATEGY strategy_routine; -DOS_DD_INTERRUPT interrupt_routine; - -/** This is the actual .SYS header! */ -const DOS_DD_HEADER __based(__segname("_CODE")) header = { - MK_FP(-1, -1), - DOS_DD_BLOCK | DOS_BLOCK_SUPPORT_32BIT_ADDRESSES - | DOS_BLOCK_FORMAT_NON_IBM | DOS_BLOCK_SUPPORT_REMOVABLE_MEDIA - | DOS_DD_SUPPORT_IOCTL_RW - | DOS_BLOCK_SUPPORT_IOCTL_GENERIC | DOS_DD_SUPPORT_IOCTL_QUERY, - strategy_routine, - interrupt_routine, - "\1CHUDISK" -}; - -/** Strategy routine saves DRS here for interrupt routine to use */ -DOS_DRS far * saved_drs; - -// Some settings -/** The ch375 io port base. */ -unsigned short ch375_port; -/** Number of units handled by the driver (i.e. number of drive letters reserved. ) */ -unsigned char num_units; -/** Read-only mode (as if write-protected floppy) */ -bool read_only; -/** Process FAT32 partitions too. */ -bool show_fat32; - -/* Runtime data for the driver. */ -struct chudisk_data data; - -/** Buffer used to load boot sectors from disk and partitions. */ -char buffer[512]; - -/** Waits for the CH375 to generate an interrupt, returns result value. - * @param max_ticks max BIOS ticks to wait for interrupt. */ -uint8_t wait_for_ch375_result(unsigned int max_ticks) -{ - uint16_t last = bda_get_tick_count_lo(); - while (!ch375_poll_interrupt() && max_ticks > 0) { - uint16_t now = bda_get_tick_count_lo(); - if (now != last) { - last = now; - max_ticks--; - } - } - if (ch375_poll_interrupt()) { - uint8_t status = ch375_get_int_status(); - //("CH375 INT status=0x%x\n", status); - return status; - } else { - dprintf("CH375 int timeout\n"); - return 0; - } -} - -/** Gets the disk sector from RW command DRS. */ -static inline uint32_t get_start_sector(const DOS_DRS far * drs) -{ - return (drs->cmd.rw.start_sector == 0xFFFF - ? drs->cmd.rw.start_sector_huge - : drs->cmd.rw.start_sector); -} - -static inline uint32_t chs_to_lba(const DOS_BPB * bpb, uint16_t cylinder, uint16_t head, uint16_t sector) -{ - uint16_t track = cylinder * bpb->numberOfHeads + head; - return mul(track, bpb->sectorsPerTrack) + (sector - 1); -} - -/** Read N sectors from the disk - * @return number of sectors read - */ -static unsigned disk_read_sectors(uint8_t far *buffer, uint32_t lba, uint8_t num) -{ - uint8_t result; - unsigned total = 0; // Total bytes written - - dprintf("disk read buf=%Fp sector=%lu num=%u\n", buffer, lba, num); - - // Initiate read operation - ch375_disk_read(lba, num); - - // Now keep going as long as ch375 says to keep going - while ((result = wait_for_ch375_result(10)) == CH375_INT_USB_DISK_READ) { - unsigned len; - - // Sanity check - if (total / SECTOR_SIZE >= num) { - dputs("read overflow"); - break; - } - - ch375_send_command(CH375_RD_USB_DATA); - len = ch375_read_data_block(&buffer[total]); - - //dprintf(" disk read data block len = %d\n", len); - - if (len == 0) { - // Done? - break; - } - - total += len; - - // Continue - ch375_send_command(CH375_DISK_RD_GO); - } - - dprintf(" disk read total bytes = %u, sectors = %u, final status = 0x%hx\n", - total, total / SECTOR_SIZE, result); - - return total / SECTOR_SIZE; -} - -/** Write N sectors to the disk - * @return number of sectors written - */ -static unsigned disk_write_sectors(uint8_t far *buffer, uint32_t lba, uint8_t num) -{ - uint8_t result; - unsigned total = 0; // Total bytes written - - dprintf("disk write buf=%Fp sector=%lu num=%u\n", buffer, lba, num); - - // Initiate operation - ch375_disk_write(lba, num); - - // Now keep going as long as ch375 says to keep going - while ((result = wait_for_ch375_result(10)) == CH375_INT_USB_DISK_WRITE) { - const unsigned len = 64; - - // Sanity check - if (total / SECTOR_SIZE >= num) { - dputs("write overflow"); - break; - } - - ch375_send_command(CH375_WR_USB_DATA7); - ch375_write_data_block(&buffer[total], len); - - total += len; - - // Continue - ch375_send_command(CH375_DISK_WR_GO); - } - - dprintf(" disk write total bytes = %u, sectors = %u, final status = 0x%hx\n", - total, total / SECTOR_SIZE, result); - - return total / SECTOR_SIZE; -} - -/** Enables CH375 USB host mode, initializes "disk" mode on it - * and connects to a disk. */ -static bool disk_connect() -{ - int tries; - - dputs("switch to usb host"); - ch375_set_usb_mode(CH375_USB_MODE_HOST_AUTO_SOF); - if (wait_for_ch375_result(4) != CH375_INT_USB_CONNECT) { - dputs("could not turn on usb host"); - return false; - } - - dputs("try init disk"); - ch375_disk_init(); - if (wait_for_ch375_result(20) != CH375_INT_USB_SUCCESS) { - dputs("could not init disk"); - return false; - } - - // looks like we have to keep polling by asking "is the disk ready?" - // even if each time we ask we are already polling - tries = 3; - while (tries-- > 0) { - ch375_test_disk_ready(); - if (wait_for_ch375_result(20) == CH375_INT_USB_SUCCESS) { - dputs("disk now init"); - return true; - } - bda_wait_tick(); - } - - dputs("disk failed to init"); - return false; -} - -/** Scans the disk for partitions. */ -static int disk_scan() -{ - uint32_t next_sector; - int num_parts, i; - - dprintf("disk scan show_fat32=%d\n", show_fat32); - - // Clear current partitions - for (i = 0; i < num_units; ++i) { - data.unit[i].exists = false; - data.unit[i].ready = false; - data.unit[i].start_sector = 0; - data.unit[i].sectors = 0; - } - - // Refresh disk size - ch375_send_command(CH375_DISK_SIZE); - if (wait_for_ch375_result(4) != CH375_INT_USB_SUCCESS) { - return DOS_DD_ERR_MEDIA_UNAVAILABLE; - } - - ch375_send_command(CH375_RD_USB_DATA); - if (ch375_read_data_block(buffer) < 8) { - dprintf("disk size command return less than 8 bytes\n"); - return DOS_DD_ERR_UNKNOWN_MEDIA; - } - - data.sectors = SWAPNC_32(*(uint32_t*)&buffer[0]); - data.bytesPerSector = SWAPNC_32(*(uint32_t*)&buffer[4]); - - dprintf("sectors %lu bytesPerSector %lu\n", data.sectors, data.bytesPerSector); - if (data.bytesPerSector != SECTOR_SIZE) { - return DOS_DD_ERR_UNKNOWN_MEDIA; - } - - // Start creating partitions/units from partition tables - num_parts = 0; - - // Start with MBR at sector 0 - next_sector = 0; - do { - const MBR_PARTENTRY *partentry; - const uint32_t this_sector = next_sector; - - dprintf("reading boot record at sector %lu\n", this_sector); - - // Try to read sector - if (disk_read_sectors(buffer, this_sector, 1) != 1) { - // Fatal for MBR, non-fatal for EBR - if (this_sector == 0) return DOS_DD_ERR_READ_FAULT; - break; - } - - // Check for signature - if (*(uint16_t*)&buffer[510] != 0xAA55U) { - dprintf(" invalid MBR signature %x\n", *(uint16_t*)&buffer[510]); - if (this_sector == 0) return DOS_DD_ERR_UNKNOWN_MEDIA; - break; - } - - // Parse the MBR - partentry = (MBR_PARTENTRY*)&buffer[446]; - - next_sector = 0; // If there is another EBR we will store it here - for (i = 0; i < 4 && num_parts < MAX_UNITS; i++) { - dprintf(" partition %d type %x start %lu len %lu\n", i, partentry[i].type, partentry[i].lbaFirst, partentry[i].lbaLen); - - if (partentry[i].type == MBR_FAT12 || partentry[i].type == MBR_FAT16_32MB - || partentry[i].type == MBR_FAT16 || partentry[i].type == MBR_FAT16_LBA - || (show_fat32 && (partentry[i].type == MBR_FAT32 || partentry[i].type == MBR_FAT32_LBA)) ) { - data.unit[num_parts].exists = true; - data.unit[num_parts].start_sector = this_sector + partentry[i].lbaFirst; - data.unit[num_parts].sectors = partentry[i].lbaLen; - dprintf(" added as unit %u starts at %lu\n", num_parts, data.unit[num_parts].start_sector); - num_parts++; - } else if (partentry[i].type == MBR_EXT || partentry[i].type == MBR_EXT_LBA) { - // Go to the EBR next - next_sector = this_sector + partentry[i].lbaFirst; - } - } - } while (next_sector != 0 && num_parts < MAX_UNITS); - - // OK! - dprintf("found %u partitions\n", num_parts); - data.ready = true; - - return 0; -} - -static int disk_init() -{ - ch375_test_disk_ready(); - if (wait_for_ch375_result(4) != CH375_INT_USB_SUCCESS) { - // Disk is not even connected, try to connect - if (!disk_connect()) { - // Nothing works: fail - return DOS_DD_ERR_DRIVE_NOT_READY; - } - } - // Disk is now ready: initiate scan for partitions - return disk_scan(); -} - -/** Return the volume label from the cached BPB, i.e. from the last time the drive was accessed. */ -static inline const char * unit_get_volume_label(uint8_t unit) -{ - const DOS_BPB *bpb = &data.unit[unit].bpb; - - if (bpb->sectorsPerFAT != 0) { - // FAT16 - return bpb->fat16.volumeLabel; - } else { - // FAT32 - return bpb->fat32.volumeLabel; - } -} - -/** Try to initialize logigcal drive 'unit'. This also initializes the entire disk - * if it was not done yet (scanning all partitions). - * It will read the BPB of the corresponding partition and cache it. */ -static int unit_init(uint8_t unit) -{ - DOS_BPB *bpb = &data.unit[unit].bpb; - int err; - - // Check if disk is ready, and initialize it if not - if (!data.ready) { - if (err = disk_init()) { - return err; - } - } - - // Disk should be ready, partitions enumerated - if (!data.unit[saved_drs->unit].exists) { - dputs("disk is ready, but partition does not exist"); - return DOS_DD_ERR_MEDIA_UNAVAILABLE; - } - - // Try to read starting sector of partition into buffer - if (disk_read_sectors(buffer, data.unit[unit].start_sector, 1) != 1) { - // Cannot read start sector... - return DOS_DD_ERR_READ_FAULT; - } - - // For now, copy largest possible BPB into our internal cache - memcpy(bpb, &buffer[DOS_BPB_OFFSET_ON_DISK], sizeof(DOS_BPB)); - - dprintf("unit %u BPB sectorsPerCluster=%u sectorsPerTrack=%u numberOfHeads=%u\n", - unit, bpb->sectorsPerCluster, - bpb->sectorsPerTrack, bpb->numberOfHeads); - dprintf(" bytesPerSector=%u numberOfFats=%u sectorsPerFat=%u \n", - bpb->bytesPerSector,bpb->numberOfFATs, - bpb->sectorsPerFAT); - dprintf(" numberOfSectors=%u numberOfSectorsLarge=%lu\n", - bpb->numberOfSectors, bpb->numberOfSectorsLarge); - - if (bpb->sectorsPerFAT != 0) { - // FAT12/16 - dprintf(" %s volumeSerialNumber=%lx volumeLabel='%.11s' fsType='%.8s'\n", - "FAT16", bpb->fat16.volumeSerialNumber, bpb->fat16.volumeLabel, bpb->fat16.fsType); - } else if (bpb->fat32.sectorPerFatLarge != 0) { - dprintf(" %s volumeSerialNumber=%lx volumeLabel='%.11s' fsType='%.8s'\n", - "FAT32", bpb->fat32.volumeSerialNumber, bpb->fat32.volumeLabel, bpb->fat32.fsType); - } else { - dprintf(" -> unknown filesystem\n"); - return DOS_DD_ERR_UNKNOWN_MEDIA; - } - - data.unit[saved_drs->unit].ready = true; - return 0; -} - -static bool unit_check_media_changed(uint8_t unit) -{ - if (data.ready) { - // Recheck if disk is still ready - ch375_test_disk_ready(); - if (wait_for_ch375_result(4) == CH375_INT_USB_SUCCESS) { - // Disk is ready. But is partition ready ? - if (data.unit[unit].ready) { - // Disk ready, partition ready. Perfect! - dputs("mediaquery -> disk ready -> unit ready -> not changed"); - return false; - } else { - // Disk ready, but this partition is not. - // Likely first time access for this partition. - dputs("mediaquery -> disk ready -> unit !ready -> changed"); - return true; - } - } else { - // CH375 is not ready but we thought it was. USB has been removed! - dputs("mediaquery -> disk not ready -> changed"); - // Invalidate disk ready flag so that we rescan for partitions - data.ready = false; - return true; - } - } else { - // We do not think CH375 is ready. First time we check, or USB not present. - dputs("mediaquery -> !ready -> changed"); - return true; - } -} - -void strategy_routine(DOS_DRS __far *drs) -{ - load_ds_from_cs(); - //dprintf("strategy routine drs=%Fp len=%u func=%u\n", drs, drs->len, drs->function); - saved_drs = drs; -} - -void interrupt_routine() -{ - int err; - load_ds_from_cs(); - //dprintf("interrupt routine drs=%Fp len=%u func=%u\n", saved_drs, saved_drs->len, saved_drs->function); - switch (saved_drs->function) { - case DOS_DD_FUNC_INIT: - driver_init(saved_drs); - break; - case DOS_DD_FUNC_MEDIA_CHECK: - if (saved_drs->unit >= num_units) { - saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; - return; - } - saved_drs->cmd.mediacheck.out.result = unit_check_media_changed(saved_drs->unit) - ? DOS_DD_MEDIAQUERY_CHANGED : DOS_DD_MEDIAQUERY_NOT_CHANGED; - saved_drs->cmd.mediacheck.out.volume_id = unit_get_volume_label(saved_drs->unit); - saved_drs->status = DOS_DD_STATUS_DONE; - break; - case DOS_DD_FUNC_BUILD_BPB: - dprintf("build bpb unit=%hu\n", saved_drs->unit); - if (saved_drs->unit >= num_units) { - dputs("unit is unknown"); - saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; - return; - } - // Try to initialize disk and unit - if (err = unit_init(saved_drs->unit)) { - saved_drs->status = err | DOS_DD_STATUS_ERROR_DONE; - return; - } - // Return pointer to our cached BPB - saved_drs->cmd.buildbpb.out.bpb = &data.unit[saved_drs->unit].bpb; - saved_drs->status = DOS_DD_STATUS_DONE; - break; - case DOS_DD_FUNC_READ: - dprintf("read unit=%hu sector=%u large_sector=%lu len=%u\n", saved_drs->unit, - saved_drs->cmd.rw.start_sector, saved_drs->cmd.rw.start_sector_huge, saved_drs->cmd.rw.len); - if (saved_drs->unit >= num_units) { - saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; - return; - } - if (!data.ready || !data.unit[saved_drs->unit].ready) { - dputs("partition not ready"); - // We don't try to initialize the drive here, DOS must do that by calling buildBPB - saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; - return; - } - if (saved_drs->cmd.rw.len > 128) { - // Too many sectors at once! - // TODO: Should we allow partial reads? When I tried even "type " crashes. - // TODO: Should we also check for segment wraparound? - saved_drs->cmd.rw.len = 0; - saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE; - return; - } else if (saved_drs->cmd.rw.len >= 1) { - uint32_t sector = get_start_sector(saved_drs); - if (sector > data.unit[saved_drs->unit].sectors) { - saved_drs->cmd.rw.len = 0; - saved_drs->status = DOS_DD_ERR_SECTOR_NOT_FOUND | DOS_DD_STATUS_ERROR_DONE; - return; - } - // Make absolute - sector += data.unit[saved_drs->unit].start_sector; - if (sector > data.sectors) { - saved_drs->cmd.rw.len = 0; - saved_drs->status = DOS_DD_ERR_SEEK | DOS_DD_STATUS_ERROR_DONE; - return; - } - - saved_drs->cmd.rw.len = disk_read_sectors(saved_drs->cmd.rw.buffer, sector, saved_drs->cmd.rw.len); - if (saved_drs->cmd.rw.len == 0) { - // Not sure if required - saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE; - return; - } - } - saved_drs->cmd.rw.volume_id = unit_get_volume_label(saved_drs->unit); - saved_drs->status = DOS_DD_STATUS_DONE; - break; - case DOS_DD_FUNC_WRITE: - case DOS_DD_FUNC_WRITE_VERIFY: // TODO Not doing the verify yet part... - dprintf("write unit=%hu sector=%u large_sector=%lu len=%u\n", saved_drs->unit, - saved_drs->cmd.rw.start_sector, saved_drs->cmd.rw.start_sector_huge, saved_drs->cmd.rw.len); - if (saved_drs->unit >= num_units) { - saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; - return; - } - if (!data.ready || !data.unit[saved_drs->unit].ready) { - dputs("partition not ready"); - saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; - return; - } - if (read_only) { - saved_drs->status = DOS_DD_ERR_WRITE_PROTECT | DOS_DD_STATUS_ERROR_DONE; - return; - } - if (saved_drs->cmd.rw.len > 128) { - // Too much! - saved_drs->cmd.rw.len = 0; - saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE; - return; - } else if (saved_drs->cmd.rw.len >= 1) { - // Check relative sector value against partition limit - uint32_t sector = get_start_sector(saved_drs); - if (sector > data.unit[saved_drs->unit].sectors) { - saved_drs->cmd.rw.len = 0; - saved_drs->status = DOS_DD_ERR_SECTOR_NOT_FOUND | DOS_DD_STATUS_ERROR_DONE; - return; - } - // Make absolute and check against disk limit - sector += data.unit[saved_drs->unit].start_sector; - if (sector > data.sectors) { - saved_drs->cmd.rw.len = 0; - saved_drs->status = DOS_DD_ERR_SEEK | DOS_DD_STATUS_ERROR_DONE; - return; - } - - saved_drs->cmd.rw.len = disk_write_sectors(saved_drs->cmd.rw.buffer, sector, saved_drs->cmd.rw.len); - if (saved_drs->cmd.rw.len == 0) { - // Not sure if required - saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE; - return; - } - - if (saved_drs->function == DOS_DD_FUNC_WRITE_VERIFY) { - // Verify one sector at a time. Use our internal buffer. - uint8_t far * origbuf = saved_drs->cmd.rw.buffer; - unsigned i; - for (i = 0; i < saved_drs->cmd.rw.len; i++) { - dprintf(" verifying sector %u\n", i); - if (disk_read_sectors(buffer, sector + i, 1) != 1) { - saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE; - return; - } - - if (_fmemcmp(&origbuf[i * SECTOR_SIZE], buffer, SECTOR_SIZE) != 0) { - dprintf(" cmp error for sec %u\n", i); - saved_drs->status = DOS_DD_ERR_CRC | DOS_DD_STATUS_ERROR_DONE; - return; - } - } - } - } - saved_drs->cmd.rw.volume_id = unit_get_volume_label(saved_drs->unit); - saved_drs->status = DOS_DD_STATUS_DONE; - break; - case DOS_DD_FUNC_DEVICE_OPEN: - dputs("device open"); - // Do nothing here -- we init the device on build bpb, as with older DOSes - saved_drs->status = DOS_DD_STATUS_DONE; - break; - case DOS_DD_FUNC_DEVICE_CLOSE: - dputs("device close"); - saved_drs->status = DOS_DD_STATUS_DONE; - break; - case DOS_DD_FUNC_REMOVEABLE_MEDIA: - dputs("removable device"); - // If the disk is fixed, we must set "busy" bit here. We are removable. - saved_drs->status = DOS_DD_STATUS_DONE; - break; - case DOS_DD_FUNC_IOCTL_READ: - dprintf("ioctl read unit=%hu buf=%Fp len=%u\n", saved_drs->unit, - saved_drs->cmd.ioctlrw.buffer, saved_drs->cmd.ioctlrw.len); - if (saved_drs->unit >= num_units) { - saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; - return; - } - // If there's enough space, return our internal config string. - if (saved_drs->cmd.ioctlrw.len >= 3 + 2 + 1 + 5 + 1) { - uint8_t far * buf = saved_drs->cmd.ioctlrw.buffer; - int len = 0; - // B = base port (2 byte) - buf[len++] = 'B'; - _fmemcpy(&buf[len], &ch375_port, sizeof(ch375_port)); - len += sizeof(ch375_port); - // 3 = showing FAT32 (1 byte) - buf[len++] = '3'; - buf[len++] = show_fat32; - // R = read only (1 byte) - buf[len++] = 'R'; - buf[len++] = read_only; - // S = partition start (4 bytes) - buf[len++] = 'S'; - _fmemcpy(&buf[len], &data.unit[saved_drs->unit].start_sector, sizeof(uint32_t)); - len += sizeof(uint32_t); - buf[len++] = '\0'; - saved_drs->cmd.ioctlrw.len = len; - } else { - saved_drs->cmd.ioctlrw.len = 0; - } - saved_drs->status = DOS_DD_STATUS_DONE; - break; - case DOS_DD_FUNC_IOCTL_WRITE: - dprintf("ioctl write unit=%hu buf=%Fp len=%u\n", saved_drs->unit, - saved_drs->cmd.ioctlrw.buffer, saved_drs->cmd.ioctlrw.len); - if (saved_drs->unit >= num_units) { - saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; - return; - } - if (saved_drs->cmd.ioctlrw.len >= 2) { - uint8_t far * buf = saved_drs->cmd.ioctlrw.buffer; - int i = 0; - do { - char c = buf[i++]; - switch (c) { - case 'S': // Partition start (5 bytes) - _fmemcpy(&data.unit[saved_drs->unit].start_sector, &buf[i], sizeof(uint32_t)); - i += sizeof(uint32_t); - dprintf(" unit=%hu new start=%lu\n", saved_drs->unit, data.unit[saved_drs->unit].start_sector); - // Mark unit as valid but needing refresh - data.unit[saved_drs->unit].exists = true; - data.unit[saved_drs->unit].ready = false; - break; - case 'L': // Partition len (5 bytes) - _fmemcpy(&data.unit[saved_drs->unit].sectors, &buf[i], sizeof(uint32_t)); - i += sizeof(uint32_t); - dprintf(" unit=%hu new len=%lu\n", saved_drs->unit, data.unit[saved_drs->unit].sectors); - // Mark unit as valid but needing refresh - data.unit[saved_drs->unit].exists = true; - data.unit[saved_drs->unit].ready = false; - break; - case '\0': - break; - default: - dprintf(" invalid control string char '%c' at %d\n", c, i-1); - saved_drs->cmd.ioctlrw.len = i; - saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE; - return; - } - } while (i < saved_drs->cmd.ioctlrw.len); - } else { - saved_drs->cmd.ioctlrw.len = 0; - saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE; - return; - } - saved_drs->status = DOS_DD_STATUS_DONE; - break; - case DOS_DD_FUNC_IOCTL_GENERIC: - dprintf("ioctl unit=%hu %x:%x buf=%Fp\n", saved_drs->unit, - saved_drs->cmd.ioctl.category, saved_drs->cmd.ioctl.minor, saved_drs->cmd.ioctl.data); - if (saved_drs->unit >= num_units) { - saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; - return; - } - // DOS can call these functions without doing a mediacheck/buildBPB - // Do not want to "forget" about a media change, so can't just call buildBPB here. - // But some functions (like get device parameters?) seem to act like a buildBPB. - if (unit_check_media_changed(saved_drs->unit)) { - // Init the disk, but not the partition, until we are sure we want to. - if (!data.ready) { - if (err = disk_init()) { - // Cannot access to disk or scan for partitions? No IOCTLs for you! - saved_drs->status = err | DOS_DD_STATUS_ERROR_DONE; - return; - } - } - } - if (saved_drs->cmd.ioctl.category == DOS_IOCTL_DISK) { - DOS_IOCTL_DISK_DATA far * iod = saved_drs->cmd.ioctl.data; - CHUDISK_IOCTL_DISK_DATA far * ciod = saved_drs->cmd.ioctl.data; - DOS_BPB * bpb = &data.unit[saved_drs->unit].bpb; - - switch (saved_drs->cmd.ioctl.minor) { - case DOS_IOCTL_DISK_GET_DEVICE_PARAMETERS: // Get CHS values (full disk!) and BPB - dprintf(" get_device_parameters specialFunctions=0x%x\n", iod->devparams.specialFunctions); - // If partition's BPB is not cached, read it from the disk - if (!data.unit[saved_drs->unit].ready) { - // This acts as if buildBPB was called - if ((err = unit_init(saved_drs->unit))) { - // TODO Now would be a good time to talk about a default BPB... - saved_drs->status = err | DOS_DD_STATUS_ERROR_DONE; - return; - } - } - - iod->devparams.specialFunctions = DOS_DEVICEPARAMS_USE_DEVICE_BPB | DOS_DEVICEPARAMS_REGULARTRACKS; - iod->devparams.deviceType = DOS_DEVICEPARAMS_HDD; - iod->devparams.deviceAttributes = DOS_DEVICEPARAMS_REMOVABLE; - if (bpb->numberOfHeads && bpb->sectorsPerTrack) { - // Compute number of cylinders of entire disk based on this part's BPB... - iod->devparams.numberOfCylinders = - div(data.sectors, bpb->numberOfHeads * bpb->sectorsPerTrack); - // TODO Check for overflows... - } else { - iod->devparams.numberOfCylinders = 0; - } - iod->devparams.mediaType = 0; - - // Return only DOS3.x BPB -- not sure if caller will have space for anything larger. - _fmemcpy(&iod->devparams.bpb, bpb, sizeof(DOS_BPB_DOS3)); - - dprintf(" get_device_parameters -> numberOfCylinders=%u\n", iod->devparams.numberOfCylinders); - - saved_drs->status = DOS_DD_STATUS_DONE; - return; - case DOS_IOCTL_DISK_GET_MEDIA_ID: // Get the volume label - if (!data.unit[saved_drs->unit].ready) { - // TODO Should we reload BPB here like above? - dputs(" but partition is not ready"); - saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; - return; - } - iod->mid.infoLevel = 0; - if (bpb->sectorsPerFAT != 0) { - iod->mid.serialNumber = bpb->fat16.volumeSerialNumber; - _fmemcpy(iod->mid.volumeLabel, bpb->fat16.volumeLabel, 11); - _fmemcpy(iod->mid.fsType, bpb->fat16.fsType, 8); - } else if (bpb->fat32.sectorPerFatLarge != 0) { - iod->mid.serialNumber = bpb->fat32.volumeSerialNumber; - _fmemcpy(iod->mid.volumeLabel, bpb->fat32.volumeLabel, 11); - _fmemcpy(iod->mid.fsType, bpb->fat32.fsType, 8); - } - dprintf(" get media id -> volumeSerialNumber=%lx volumeLabel='%.11Fs' fsType='%.8Fs'\n", - iod->mid.serialNumber, iod->mid.volumeLabel, iod->mid.fsType); - saved_drs->status = DOS_DD_STATUS_DONE; - return; - case DOS_IOCTL_DISK_SET_MEDIA_ID: // Set the volume label, etc. - if (!data.unit[saved_drs->unit].exists) { - dputs(" but partition does not exist!"); - saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; - return; - } - dprintf(" set media id to volumeSerialNumber=%lx volumeLabel='%.11Fs' fsType='%.8Fs'\n", - iod->mid.serialNumber, iod->mid.volumeLabel, iod->mid.fsType); - if (read_only) { - saved_drs->status = DOS_DD_ERR_WRITE_PROTECT | DOS_DD_STATUS_ERROR_DONE; - return; - } - // Here we always reload BPB -- do not want to write back outdated stuff. - if (disk_read_sectors(buffer, data.unit[saved_drs->unit].start_sector, 1) != 1) { - // Cannot read start sector... - saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE; - return; - } - bpb = (DOS_BPB*) &buffer[DOS_BPB_OFFSET_ON_DISK]; - if (bpb->sectorsPerFAT != 0) { - bpb->fat16.volumeSerialNumber = iod->mid.serialNumber; - _fmemcpy(bpb->fat16.volumeLabel, iod->mid.volumeLabel, 11); - _fmemcpy(bpb->fat16.fsType, iod->mid.fsType, 8); - } else if (bpb->fat32.sectorPerFatLarge != 0) { - bpb->fat32.volumeSerialNumber = iod->mid.serialNumber; - _fmemcpy(bpb->fat32.volumeLabel, iod->mid.volumeLabel, 11); - _fmemcpy(bpb->fat32.fsType, iod->mid.fsType, 8); - } else { - saved_drs->status = DOS_DD_ERR_UNKNOWN_MEDIA | DOS_DD_STATUS_ERROR_DONE; - return; - } - if (disk_write_sectors(buffer, data.unit[saved_drs->unit].start_sector, 1) != 1) { - // Cannot re-write start sector... - saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE; - return; - } - // Re-sync our internal BPB - memcpy(&data.unit[saved_drs->unit].bpb, bpb, sizeof(DOS_BPB)); - saved_drs->status = DOS_DD_STATUS_DONE; - return; - case DOS_IOCTL_DISK_READ_TRACK_ON_LOGICAL_DEVICE: // Absolute read - dprintf(" read track cylinder %u head %u sector %u sectors %u buf %Fp -> lba %lu\n", - iod->rw.cylinder, iod->rw.head, iod->rw.firstSector, iod->rw.sectors, iod->rw.buffer, - chs_to_lba(bpb, iod->rw.cylinder, iod->rw.head, iod->rw.firstSector + 1)); - // Need the partition's BPB to map CHS to LBA... - if (!data.unit[saved_drs->unit].ready) { - saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; - return; - } - if (disk_read_sectors(iod->rw.buffer, chs_to_lba(bpb, iod->rw.cylinder, iod->rw.head, iod->rw.firstSector + 1), iod->rw.sectors) != iod->rw.sectors) { - saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE; - return; - } - saved_drs->status = DOS_DD_STATUS_DONE; - return; - case DOS_IOCTL_DISK_WRITE_TRACK_ON_LOGICAL_DEVICE: - dprintf(" write track cylinder %u head %u sector %u sectors %u buf %Fp -> lba %lu\n", - iod->rw.cylinder, iod->rw.head, iod->rw.firstSector, iod->rw.sectors, iod->rw.buffer, - chs_to_lba(bpb, iod->rw.cylinder, iod->rw.head, iod->rw.firstSector + 1)); - // Need the partition's BPB to map CHS to LBA... - if (!data.unit[saved_drs->unit].ready) { - saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; - return; - } - if (read_only) { - saved_drs->status = DOS_DD_ERR_WRITE_PROTECT | DOS_DD_STATUS_ERROR_DONE; - return; - } - if (disk_write_sectors(iod->rw.buffer, chs_to_lba(bpb, iod->rw.cylinder, iod->rw.head, iod->rw.firstSector + 1), iod->rw.sectors) != iod->rw.sectors) { - saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE; - return; - } - saved_drs->status = DOS_DD_STATUS_DONE; - return; - case DOS_IOCTL_DISK_FORMAT_TRACK_ON_LOGICAL_DEVICE: - dprintf(" formatting track cylinder %u head %u\n", - iod->fv.cylinder, iod->fv.head); - if (!data.unit[saved_drs->unit].exists) { - dputs(" but partition does not exist!"); - saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; - return; - } - if (read_only) { - saved_drs->status = DOS_DD_ERR_WRITE_PROTECT | DOS_DD_STATUS_ERROR_DONE; - return; - } - // Total nop. Should we zerofill the track or something? - iod->fv.specialFunctions = 1; // Not supported by BIOS? - saved_drs->status = DOS_DD_STATUS_DONE; - return; - case CHUDISK_IOCTL_DISK_READ_ABS_SECTOR: - dprintf(" read abs sector %lu sectors %u buf %Fp\n", - ciod->rw.sector, ciod->rw.sectors, ciod->rw.buffer); - if (disk_read_sectors(ciod->rwabs.buffer, ciod->rwabs.sector, ciod->rwabs.sectors) != ciod->rwabs.sectors) { - saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE; - return; - } - saved_drs->status = DOS_DD_STATUS_DONE; - return; - } - } - - dputs(" ioctl not supported\n"); - saved_drs->status = DOS_DD_ERR_UNKNOWN_COMMAND | DOS_DD_STATUS_ERROR_DONE; - break; - case DOS_DD_FUNC_GET_LOGICAL_DEVICE: - dprintf("get logical device unit=%hu\n", saved_drs->unit); - saved_drs->unit = 0; - saved_drs->status = DOS_DD_STATUS_DONE; - break; - case DOS_DD_FUNC_SET_LOGICAL_DEVICE: - // We do not support this. Ignore it. - dprintf("set logical device unit=%hu\n", saved_drs->unit); - saved_drs->status = DOS_DD_STATUS_DONE; - break; - case DOS_DD_FUNC_IOCTL_QUERY: - dprintf("ioctl query %x:%x buf=%Fp\n", saved_drs->cmd.ioctl.category, saved_drs->cmd.ioctl.minor, saved_drs->cmd.ioctl.data); - switch (saved_drs->cmd.ioctl.category) { - case DOS_IOCTL_DISK: - switch (saved_drs->cmd.ioctl.minor) { - case DOS_IOCTL_DISK_GET_DEVICE_PARAMETERS: - case DOS_IOCTL_DISK_GET_MEDIA_ID: - case DOS_IOCTL_DISK_SET_MEDIA_ID: - case DOS_IOCTL_DISK_READ_TRACK_ON_LOGICAL_DEVICE: - case DOS_IOCTL_DISK_WRITE_TRACK_ON_LOGICAL_DEVICE: - case DOS_IOCTL_DISK_FORMAT_TRACK_ON_LOGICAL_DEVICE: - case CHUDISK_IOCTL_DISK_READ_ABS_SECTOR: - // Supported - saved_drs->status = DOS_DD_STATUS_DONE; - return; - } - break; - } - // Unsupported - saved_drs->status = DOS_DD_ERR_UNKNOWN_COMMAND | DOS_DD_STATUS_ERROR_DONE; - break; - default: - dprintf("unknown function received %u\n", saved_drs->function); - break; - } -} - -// Required by Watcom ABI -- since we do not link to the runtime -void _small_code() {} - -// Symbol to let initialization code know the end of resident data -char resident_end; diff --git a/chudisk.h b/chudisk.h deleted file mode 100644 index f3761fb..0000000 --- a/chudisk.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * CHU - driver for CH375 in disk mode - * Copyright (C) 2024 Javier S. Pedro - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef CHUDISK_H -#define CHUDISK_H - -#include - -#include "inc/dosdrv.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 unsigned char num_units; - -extern bool read_only; - -extern bool show_fat32; - -extern struct chudisk_data -{ - /** Pointers to all BPBs, to return to DOS during init. */ - DOS_BPB * bpb_ptrs[MAX_UNITS]; - - /** Whether we are ready to handle IO, i.e. disk init, partitions scanned. */ - bool ready; - - uint32_t sectors; - uint32_t bytesPerSector; - - struct chudisk_unit { - /** Whether the partition exists (start_sector points to it) */ - bool exists; - /** Note in theory may be zero (superfloppy) */ - uint32_t start_sector; - /** Number of sectors for this partition. */ - uint32_t sectors; - /** Whether the partition's BPB was loaded. */ - bool ready; - /** DOS device params (contains BPB), only valid if 'ready'. */ - DOS_BPB bpb; - } unit[MAX_UNITS]; -} data; - -// Functions called by both init part and resident part -extern void driver_init(DOS_DRS __far *drs); - -extern char resident_end; - -extern uint8_t wait_for_ch375_result(unsigned int max_ticks); - - -// CHUDISK IOCTLs -// These are used with the DISK major -// TODO Not sure we can really happily add custom minors here, -// but then again DOS outright refuses to accept any other major -enum CHUDISK_IOCTL_DISK_MINOR { - CHUDISK_IOCTL_DISK_READ_ABS_SECTOR = 0xC1 -}; - -typedef _Packed union CHUDISK_IOCTL_DISK_DATA -{ - struct CHUDISK_RWABSSECTOR { - uint32_t sector; - uint8_t sectors; - void far * buffer; - } rwabs; -} CHUDISK_IOCTL_DISK_DATA; - -#endif // CHUDISK_H diff --git a/chudisk.lnk b/chudisk.lnk deleted file mode 100644 index bec3794..0000000 --- a/chudisk.lnk +++ /dev/null @@ -1,4 +0,0 @@ -format dos com - -option statics -option map=chudisk.map diff --git a/cio.c b/cio.c deleted file mode 100644 index 43a2a27..0000000 --- a/cio.c +++ /dev/null @@ -1,147 +0,0 @@ -/* - * CHU - driver for CH375 in disk mode - * Copyright (C) 2024 Javier S. Pedro - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/* Simple printf function for console, a bit wasteful for what it is used. - * But it is part of the init code, so it will be thrown away. - * dlog.h is more powerful but only in debug builds. */ - - -#include -#include -#include -#include - -#include "inc/dosapi.h" - -#include "cio.h" - -static inline void c_llputc(char c) -{ - dos_print_char(c); -} - -void c_utoa(unsigned num, unsigned base) -{ - char buf[6]; - int i = 0; - - do { - unsigned digit = num % base; - - if (digit < 10) { - buf[i] = '0' + digit; - } else { - buf[i] = 'a' + (digit - 10); - } - - i++; - num /= base; - } while (num > 0 && i < sizeof(buf)); - - while (i--) { - c_llputc(buf[i]); - } -} - -void c_itoa(int num, unsigned base) -{ - unsigned unum; - - if (num < 0) { - c_llputc('-'); - unum = -num; - } else { - unum = num; - } - - c_utoa(unum, base); -} - -unsigned c_strtou(const char * *str) -{ - unsigned i = 0; - const char *s = *str; - char c = *s; - while (c >= '0' && c <= '9') { - i = (i * 10) + (c - '0'); - c = *(++s); - } - *str = s; - return i; -} - -static void c_printstr(const char *s) -{ - while (*s) { c_llputc(*s++); } -} - -void c_printf(const char *fmt, ...) -{ - va_list va; - char c; - - va_start(va, fmt); - - while ((c = *(fmt++))) { - if (c == '%') { - switch ((c = *(fmt++))) { - case 'd': - c_itoa(va_arg(va, int), 10); - break; - case 'u': - c_utoa(va_arg(va, unsigned), 10); - break; - case 'x': - case 'p': - c_utoa(va_arg(va, unsigned), 16); - break; - case 's': - c_printstr(va_arg(va, const char *)); - break; - case 'c': - c_putc(va_arg(va, char)); - break; - case '%': - c_llputc('%'); - break; - } - } else { - c_putc(c); - } - } - - va_end(va); -} - -void c_putc(char c) -{ - if (c == '\n') { - c_llputc('\r'); - } - c_llputc(c); -} - - -void c_puts(const char *s) -{ - char c; - while ((c = *(s++))) { - c_putc(c); - } - c_putc('\n'); -} diff --git a/cio.h b/cio.h deleted file mode 100644 index 24a67ae..0000000 --- a/cio.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * CHU - driver for CH375 in disk mode - * Copyright (C) 2024 Javier S. Pedro - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -#ifndef CIO_H -#define CIO_H - -void c_utoa(unsigned num, unsigned base); -void c_itoa(int num, unsigned base); -unsigned c_strtou(const char * *str); - -void c_printf(const char *fmt, ...); -void c_putc(char c); -void c_puts(const char *s); - -#endif // CIO_H diff --git a/dlog.h b/dlog.h deleted file mode 100644 index 00cd5b5..0000000 --- a/dlog.h +++ /dev/null @@ -1,359 +0,0 @@ -/* - * VBMouse - printf & logging routines - * Copyright (C) 2022 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef DLOG_H -#define DLOG_H - -#include -#include -#include -#include -#include - -// Customizable defines -/** If 0, these routines become nops */ -#define ENABLE_DLOG 0 -/** 1 means target serial port, 0 means target IO port. */ -#define DLOG_TARGET DLOG_TARGET_SERIAL -/** 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 - -// End of customizable defines - -#define DLOG_TARGET_BIOSTTY 1 -#define DLOG_TARGET_SERIAL 2 -#define DLOG_TARGET_IOPORT 3 - -#if ENABLE_DLOG - -/** 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 DLOG_TARGET == DLOG_TARGET_BIOSTTY - -static inline void dlog_init() -{ - // No initialization required -} - -static inline void dconputc(char c); -#pragma aux dconputc = \ - "mov ah, 0x0E" \ - "xor bx, bx" \ - "int 0x10" \ - __parm [AL] \ - __modify __exact [AH AL BH BL]; - -static inline void dputc(char c) -{ - // Need to convert CR into CRLF... - if (c == '\n') { - dconputc('\n'); - dconputc('\r'); - } else { - dconputc(c); - } -} - - -#elif DLOG_TARGET == DLOG_TARGET_SERIAL - -static void dlog_init() -{ - // Comes from https://wiki.osdev.org/Serial_Ports#Initialization - outp(DLOG_TARGET_PORT + 1, 0x00); // Disable all interrupts - outp(DLOG_TARGET_PORT + 3, 0x80); // Enable DLAB (set baud rate divisor) - outp(DLOG_TARGET_PORT + 0, 0x01); // Set divisor to 1 (lo byte) 115200 baud - outp(DLOG_TARGET_PORT + 1, 0x00); // (hi byte) - outp(DLOG_TARGET_PORT + 3, 0x03); // 8 bits, no parity, one stop bit - outp(DLOG_TARGET_PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold - outp(DLOG_TARGET_PORT + 4, 0x03); // RTS/DSR set, IRQs disabled -} - -static inline void dputc(char c) -{ - while (!(inp(DLOG_TARGET_PORT + 5) & 0x20)); - outp(DLOG_TARGET_PORT, c); -} - -#elif DLOG_TARGET == DLOG_TARGET_IOPORT - -static inline void dlog_init() -{ - // No initialization required -} - -static inline void dputc(char c) -{ - outp(DLOG_TARGET_PORT, c); -} - -#endif /* DLOG_TARGET */ - -static void d_utoa(unsigned num, unsigned base) -{ - char buf[6]; - int i = 0; - - do { - unsigned digit = num % base; - - if (digit < 10) { - buf[i] = '0' + digit; - } else { - buf[i] = 'a' + (digit - 10); - } - - i++; - num /= base; - } while (num > 0 && i < sizeof(buf)); - - while (i--) { - dputc(buf[i]); - } -} - -static void d_ultoa(unsigned long num, unsigned base) -{ - char buf[12]; - int i = 0; - - do { - unsigned digit = 0; - - static inline void uldiv(void); -#pragma aux uldiv = \ - "push eax" \ - "push ecx" \ - "push edx" \ - "mov eax, [num]" \ - "movzx ecx, [base]" \ - "xor edx, edx" \ - \ - "div ecx" \ - \ - "mov [num], eax" \ - "mov [digit], dx" \ - \ - "pop edx" \ - "pop ecx" \ - "pop eax" \ - __modify __exact []; - uldiv(); - - if (digit < 10) { - buf[i] = '0' + digit; - } else { - buf[i] = 'a' + (digit - 10); - } - - i++; - } while (num > 0 && i < sizeof(buf)); - - while (i--) { - dputc(buf[i]); - } -} - -static void d_itoa(int num, unsigned base) -{ - unsigned unum; - - if (num < 0) { - dputc('-'); - unum = -num; - } else { - unum = num; - } - - d_utoa(unum, base); -} - -static void d_ltoa(long num, unsigned base) -{ - unsigned long unum; - - if (num < 0) { - dputc('-'); - unum = -num; - } else { - unum = num; - } - - d_ultoa(unum, base); -} - -static unsigned d_strtou(const char * __far *str) -{ - unsigned i = 0; - const char *s = *str; - char c = *s; - while (c >= '0' && c <= '9') { - i = (i * 10) + (c - '0'); - c = *(++s); - } - *str = s; - return i; -} - -static void d_printstr(const char __far *s, unsigned l) -{ - while (l > 0 && *s) { - dputc(*s++); - l--; - } -} - -static void dprintf(const char *fmt, ...) -{ - va_list va; - char c; - - va_start(va, fmt); - - while ((c = *(fmt++))) { - if (c == '%') { - unsigned width, precision, size; - bool is_far = false; - - width = d_strtou(&fmt); - - if ((c = *fmt) == '.') { - fmt++; - precision = d_strtou(&fmt); - } else { - precision = UINT_MAX; - } - - switch ((c = *fmt)) { - case 'l': - size = sizeof(long); - fmt++; - break; - case 'h': - size = sizeof(short); - fmt++; - break; - case 'F': - is_far = true; - fmt++; - break; - default: - size = sizeof(int); - break; - } - - switch ((c = *fmt)) { - case 'd': - switch (size) { - case sizeof(int): - d_itoa(va_arg(va, int), 10); - break; - case sizeof(long): - d_ltoa(va_arg(va, long), 10); - break; - } - break; - case 'u': - switch (size) { - case sizeof(int): - d_utoa(va_arg(va, int), 10); - break; - case sizeof(long): - d_ultoa(va_arg(va, long), 10); - break; - } - break; - case 'x': - switch (size) { - case sizeof(int): - d_utoa(va_arg(va, int), 16); - break; - case sizeof(long): - d_ultoa(va_arg(va, long), 16); - break; - } - break; - case 'b': - switch (size) { - case sizeof(int): - d_utoa(va_arg(va, int), 2); - break; - case sizeof(long): - d_ultoa(va_arg(va, long), 2); - break; - } - break; - case 'p': - if (is_far) { - void __far *p = va_arg(va, void __far *); - d_utoa(FP_SEG(p), 16); - dputc(':'); - d_utoa(FP_OFF(p), 16); - } else { - d_utoa(va_arg(va, unsigned), 16); - } - break; - case 's': - d_printstr(is_far ? va_arg(va, const char __far *) - : va_arg(va, const char *), - precision); - break; - case 'c': - dputc(va_arg(va, char)); - break; - case '%': - dputc('%'); - break; - } - fmt++; - } else { - dputc(c); - } - } - - va_end(va); -} - -static void dputs(const char *s) -{ - char c; - while ((c = *(s++))) { - dputc(c); - } - dputc('\n'); -} - -#else /* ENABLE_DLOG */ - -#define dlog_nop() do { } while(0) -#define dlog_init() dlog_nop() -#define dputc(c) dlog_nop() -#define dputs(s) dlog_nop() -#define dprintf(...) dlog_nop() - - -#endif /* ENABLE_DLOG */ - -#endif /* DLOG_H */ 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 . + */ + +#ifndef CH375DOS_H +#define CH375DOS_H + +#include +#include +#include +#include + +/* 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/dos/chud.c b/dos/chud.c new file mode 100644 index 0000000..c6d728c --- /dev/null +++ b/dos/chud.c @@ -0,0 +1,418 @@ +/* + * CHU - driver for CH375 in disk mode + * Copyright (C) 2024 Javier S. Pedro + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "inc/mbr.h" +#include "inc/dosdrv.h" + +#include "chudisk.h" + +uint16_t ch375_port; + +bool read_only; +bool show_fat32; + +uint8_t drive; +uint32_t partition_start; +uint32_t partition_len; + +#define MAX_PARTITIONS 32 +struct partinfo { + uint32_t start_sector; + uint32_t num_sectors; + uint8_t type; +} partinfo[MAX_PARTITIONS]; +unsigned int num_parts; + +static int ioctl_read_control(uint8_t drive, void *buffer, int len) +{ + union REGS regs; + regs.h.ah = DOS_IOCTL; + regs.h.al = 0x04; // Read control string from block device + regs.x.bx = drive; + regs.x.cx = len; + regs.x.dx = FP_OFF(buffer); + + intdos(®s, ®s); + + if (regs.x.cflag) { + return -1; + } else { + return regs.x.ax; + } +} + +static int ioctl_write_control(uint8_t drive, void *buffer, int len) +{ + union REGS regs; + regs.h.ah = DOS_IOCTL; + regs.h.al = 0x05; // Write control string to block device + regs.x.bx = drive; + regs.x.cx = len; + regs.x.dx = FP_OFF(buffer); + + intdos(®s, ®s); + + if (regs.x.cflag) { + return -1; + } else { + return regs.x.ax; + } +} + +static int ioctl_read_abs_sector(uint8_t drive, void *buffer, uint32_t sector, int sectors) +{ + CHUDISK_IOCTL_DISK_DATA ciod; + union REGS regs; + + ciod.rwabs.buffer = buffer; + ciod.rwabs.sector = sector; + ciod.rwabs.sectors = sectors; + + regs.h.ah = DOS_IOCTL; + regs.h.al = 0x0D; // IOCTL for block devices + regs.x.bx = drive; + regs.h.ch = DOS_IOCTL_DISK; + regs.h.cl = CHUDISK_IOCTL_DISK_READ_ABS_SECTOR; + regs.x.dx = FP_OFF(&ciod); + + intdos(®s, ®s); + + return regs.x.cflag ? -1 : 0; +} + +static void dos_disk_reset() +{ + union REGS regs; + regs.h.ah = 0x0D; // DOS DISK RESET + + intdos(®s, ®s); +} + +static int parse_chudisk_control_string(const char *b, int len) +{ + // Not sure why I'm doing it this way instead of just sharing a pointer. + int i = 0; +#if 0 + printf("control string:\n"); + for (i = 0; i < len; i+=8) { + printf(" %hx %hx %hx %hx %hx %hx %hx %hx\n", + b[i+0], b[i+1], b[i+2], b[i+3], b[i+4], b[i+5], b[i+6], b[i+7]); + } + i = 0; +#endif + while (i < len) { + char c = b[i++]; + switch (c) { + case 'B': + memcpy(&ch375_port, &b[i], sizeof(ch375_port)); + i+=sizeof(ch375_port); + break; + case '3': + show_fat32 = b[i++]; + break; + case 'R': + read_only = b[i++]; + break; + case 'S': + memcpy(&partition_start, &b[i], sizeof(partition_start)); + i+=sizeof(partition_start); + break; + case 'L': + memcpy(&partition_len, &b[i], sizeof(partition_len)); + i+=sizeof(partition_len); + break; + case '\0': + return 0; + default: + fprintf(stderr, "Unidentified control string parameter: '%c'\n", c); + return -1; + } + } + + return 0; +} + +static bool is_selectable_partition(const MBR_PARTENTRY *partentry) +{ + return partentry->type == MBR_FAT12 || partentry->type == MBR_FAT16_32MB + || partentry->type == MBR_FAT16 || partentry->type == MBR_FAT16_LBA + || (show_fat32 && (partentry->type == MBR_FAT32 || partentry->type == MBR_FAT32_LBA)); +} + +static const char *get_partition_type(const MBR_PARTENTRY *partentry) +{ + static char buffer[8]; + switch (partentry->type) { + case MBR_EMPTY: + return "invalid"; + case MBR_FAT12: + return "fat12"; + case MBR_FAT16_32MB: + case MBR_FAT16: + case MBR_FAT16_LBA: + return "fat16"; + case MBR_IFS: + return "ntfs"; + case MBR_FAT32: + case MBR_FAT32_LBA: + return "fat32"; + case MBR_EXT: + case MBR_EXT_LBA: + return "extended"; + case MBR_LINUX: + case MBR_LINUX_SWAP: + return "linux"; + default: + sprintf(buffer, "%#X", partentry->type); + return buffer; + } +} + + +static const char * show_partition_size(const MBR_PARTENTRY *partentry) +{ + static char buffer[12]; + uint32_t size = partentry->lbaLen / 2; + STATIC_ASSERT(SECTOR_SIZE == 512); + + if (size <= 10000) { + sprintf(buffer, "%lu KB", size); + return buffer; + } + + size /= 1024; + if (size <= 10000) { + sprintf(buffer, "%lu MB", size); + return buffer; + } + + size /= 1024; + + sprintf(buffer, "%lu GB", size); + return buffer; +} + +static const char *get_partition_label(const MBR_PARTENTRY *partentry, const uint32_t start_sector) +{ + static char buffer[SECTOR_SIZE]; + DOS_BPB *bpb = (DOS_BPB*) &buffer[DOS_BPB_OFFSET_ON_DISK]; + + switch (partentry->type) { + // Focus on well-known types for now + case MBR_FAT12: + case MBR_FAT16_32MB: + case MBR_FAT16: + case MBR_FAT16_LBA: + case MBR_FAT32: + case MBR_FAT32_LBA: + break; + default: + return " "; + } + + if (ioctl_read_abs_sector(drive, buffer, start_sector, 1) != 0) { + fprintf(stderr, "while reading partition boot record at %lu: %s\n", start_sector, strerror(errno)); + return " ???? "; + } + + if (bpb->sectorsPerFAT != 0) { + // Assume FAT16 + return bpb->fat16.volumeLabel; + } else { + // Assume FAT32 + return bpb->fat32.volumeLabel; + } +} + +static void print_partition(const uint32_t mbr_sector, const MBR_PARTENTRY *partentry, bool selectable) +{ + const bool ebr = mbr_sector != 0; + const uint32_t start_sector = mbr_sector + partentry->lbaFirst; + const bool is_current = partition_start == start_sector; + const char *typeLabel = get_partition_type(partentry); + const char *volumeLabel = get_partition_label(partentry, start_sector); + const char *sizeLabel = show_partition_size(partentry); + + if (partentry->type == MBR_EMPTY) { + // Skip empty partitions no matter what + return; + } + if (ebr && (partentry->type == MBR_EXT || partentry->type == MBR_EXT_LBA)) { + // Skip displaying EXT partitions EBR, noisy + return; + } + + if (selectable) { + const unsigned index = num_parts + 1; + + printf(" %c[%2u] %8s %-11.11s %10s \n", + is_current ? '*' : ' ', index, typeLabel, volumeLabel, sizeLabel); + } else { + printf(" %c[ ] %8s %-11.11s %10s \n", + is_current ? '*' : ' ', typeLabel, volumeLabel, sizeLabel); + } + +} + +static int scan_disk_partitions() +{ + static char buffer[SECTOR_SIZE]; + uint32_t next_sector = 0; + + printf(" Index %8s %-11.11s %10s \n", + "Type", "Label", "Size"); + + // Start with MBR at sector 0 + next_sector = 0; + do { + const uint32_t this_sector = next_sector; + const MBR_PARTENTRY *partentry; + int i; + + // Try to read sector + if (ioctl_read_abs_sector(drive, buffer, this_sector, 1) != 0) { + fprintf(stderr, "while reading %s at disk sector %lu: %s\n", + this_sector == 0 ? "MBR" : "EBR", + this_sector, strerror(errno)); + // Fatal for MBR, non-fatal for EBR + if (this_sector == 0) { + fprintf(stderr, "is the disk present?\n"); + return -1; + } + break; + } + + // Check for signature + if (*(uint16_t*)&buffer[MBR_SIGNATURE_OFFSET_ON_DISK] != 0xAA55U) { + fprintf(stderr, "on disk sector %lu: invalid MBR signature %x\n", + this_sector, *(uint16_t*)&buffer[MBR_SIGNATURE_OFFSET_ON_DISK]); + if (this_sector == 0) return -1; + break; + } + + // Parse the MBR + partentry = (MBR_PARTENTRY*)&buffer[MBR_PARTENTRY_OFFSET_ON_DISK]; + + next_sector = 0; // If there is another EBR we will store it here + for (i = 0; i < 4 && num_parts < MAX_PARTITIONS; i++) { + bool is_selectable = is_selectable_partition(&partentry[i]); + + print_partition(this_sector, &partentry[i], is_selectable); + + if (is_selectable) { + partinfo[num_parts].type = partentry[i].type; + partinfo[num_parts].start_sector = this_sector + partentry[i].lbaFirst; + partinfo[num_parts].num_sectors = partentry[i].lbaLen; + num_parts++; + } + if (partentry[i].type == MBR_EXT || partentry[i].type == MBR_EXT_LBA) { + // Go to the EBR next + next_sector = this_sector + partentry[i].lbaFirst; + } + } + } while (next_sector != 0 && num_parts < MAX_PARTITIONS); + + return 0; +} + +static void usage() +{ + printf("Usage: chud X:, where X is a drive letter handled by CHUDISK.SYS\n"); +} + +int main(int argc, const char *argv[]) +{ + static char buffer[16]; + int i; + unsigned part; + + for (i = 1; i < argc; i++) { + // Drive letter? + if (argv[i][0] >= 'A' && argv[i][0] <= 'Z' && argv[i][1] == ':') { + drive = 1 + argv[i][0] - 'A'; + } else if (argv[i][0] >= 'a' && argv[i][0] <= 'z' && argv[i][1] == ':') { + drive = 1 + argv[i][0] - 'a'; + } + } + + // Now read the driver settings and current partition + i = ioctl_read_control(drive, buffer, sizeof(buffer)); + if (i < 0) { + perror("ioctl_read_control"); + usage(); + return EXIT_FAILURE; + } + + if (parse_chudisk_control_string(buffer, i)) { + fprintf(stderr, "wrong IOCTL string returned by driver\n"); + usage(); + return EXIT_FAILURE; + } + + if (scan_disk_partitions() != 0) { + return EXIT_FAILURE; + } + if (num_parts == 0) { + fprintf(stderr, "No partitions found on the disk\n"); + return EXIT_FAILURE; + } + + printf("Select partition in [1..%u] range: ", num_parts); + i = scanf("%u", &part); + + if (i != 1) { + // Assume cancel + return EXIT_FAILURE; + } + + if (part == 0 || part > num_parts) { + fprintf(stderr, "Invalid partition %u\n", part); + return EXIT_FAILURE; + } + + printf("Switching drive to partition %u at offset %lu size %lu\n", part, + partinfo[part-1].start_sector, partinfo[part-1].num_sectors); + + dos_disk_reset(); // Flush caches before we swap the drive... + + i = 0; + buffer[i++] = 'S'; + memcpy(&buffer[i], &partinfo[part-1].start_sector, sizeof(uint32_t)); + i+=sizeof(uint32_t); + buffer[i++] = 'L'; + memcpy(&buffer[i], &partinfo[part-1].num_sectors, sizeof(uint32_t)); + i+=sizeof(uint32_t); + buffer[i] = '\0'; + + if (ioctl_write_control(drive, buffer, i) < i) { + perror("ioctl_write_control"); + return EXIT_FAILURE; + } + + // The above IOCTL will also cause the next mediacheck from DOS to return changed. + + return EXIT_SUCCESS; +} diff --git a/dos/chudinit.c b/dos/chudinit.c new file mode 100644 index 0000000..3dfb782 --- /dev/null +++ b/dos/chudinit.c @@ -0,0 +1,226 @@ +/* + * CHU - driver for CH375 in disk mode + * Copyright (C) 2024 Javier S. Pedro + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include +#include + +#include + +#include "inc/dosapi.h" +#include "inc/dosint.h" +#include "inc/bda.h" + +#include "chudisk.h" +#include "dlog.h" +#include "cio.h" +#include "utils.h" + +static int parse_int(const char far * far *s) +{ + int i = 0; + while (**s >= '0' && **s <= '9') { + i = (i * 10) + (**s - '0'); + (*s)++; + } + if (**s == ' ' || **s == '\r' || **s == '\n') { + return i; + } else { + return -1; + } +} + +static int parse_hex(const char far * far *s) +{ + int i = 0; + while ((**s >= '0' && **s <= '9') || (**s >= 'A' && **s <= 'F') || (**s >= 'a' && **s <= 'f')) { + if (**s >= '0' && **s <= '9') i = (i * 16) + (**s - '0'); + else if (**s >= 'A' && **s <= 'F') i = (i * 16) + (**s - 'A'); + else if (**s >= 'a' && **s <= 'f') i = (i * 16) + (**s - 'a'); + (*s)++; + } + if (**s == ' ' || **s == '\r' || **s == '\n') { + return i; + } else { + return -1; + } +} + +static bool parse_cmdline(const char far *cmdline) +{ + // Source command line string + // E.g. DEVICE.SYS /N3\r\n + const char far *s = cmdline; + + // Skip filename part (search for first sep) + while (*s != ' ' && *s != '\r' && *s != '\n') { s++; } + if (*s == '\r' || *s == '\n') return true; // No arguments + // Now skip whitespace + while (*s == ' ') { s++; } + + // Now handle options like /N3 /X4 + while (*s != '\r' && *s != '\n') { + char c = *s++, opt; + int num; + switch (c) { + case '/': + opt = *s++; + switch (opt) { + case 'N': + case 'n': // number of units + num = parse_int(&s); + if (num > 0 && num <= MAX_UNITS) { + num_units = num; + } else { + c_printf("invalid value '%d' for option '%c'\n", num, opt); + return false; + } + break; + case 'P': + case 'p': // IO port + num = parse_hex(&s); + if (num > 0) { + ch375_dev.io_base = num; + } else { + c_printf("invalid value '%d' for option '%c'\n", num, opt); + return false; + } + break; + case 'R': + case 'r': // Read only + read_only = true; + break; + case '3': // Show FAT32 partitions + show_fat32 = true; + break; + default: + c_printf("invalid option '%c'\n", opt); + return false; + } + break; + case ' ': + break; + default: + c_printf("invalid syntax '%c'\n", c); + return false; + } + } + + dprintf("Configured num drives: %hu\n", num_units); + + return true; +} + +static void fill_initial_bpb(DOS_BPB *bpb) +{ + // Dummy, only used during driver init + // Most likely these values can be anything you want, + // as long as it remotely looks like a valid FAT16 BPB + bpb->bytesPerSector = 512; + bpb->sectorsPerCluster = 1; + bpb->reservedSectors = 0; + bpb->numberOfFATs = 2; + bpb->rootDirEntries = 16; + bpb->numberOfSectors = 512; + bpb->mediaType = DOS_BPB_MEDIA_HDD; + bpb->sectorsPerFAT = 1; + bpb->sectorsPerTrack = 63; + bpb->numberOfHeads = 255; + bpb->hiddenSectors = 0; + bpb->numberOfSectorsLarge = 0; + memcpy(bpb->fat16.volumeLabel, "NO NAME ", 11); +} + +void driver_init(DOS_DRS __far *drs) +{ + unsigned i; + uint16_t dos_version; + uint8_t dos_major, dos_minor; + uint8_t hw_version; + + dlog_init(); + + // Defaults + ch375_dev.io_base = 0x260; + num_units = 1; + read_only = false; + show_fat32 = false; + + // If DOS > 7.1, enable FAT32 by default + dos_version = dos_get_version(); + dos_major = dos_version & 0xFF; + dos_minor = (dos_version >> 8) & 0xFF; + dprintf("DOS version %d.%d\n", dos_major, dos_minor); + if (dos_major > 7 || (dos_major == 7 && dos_minor >= 10)) { + show_fat32 = true; + } + + // Parse command line arguments + if (!parse_cmdline(drs->cmd.init.in.cmdline)) { + c_puts("CHUDISK: Syntax error in cmdline arguments"); + goto fail; + } + + // Try to initialize CH375 + dputs("CH375 reset..."); + ch375_reset(&ch375_dev); + bda_wait_ticks(3); // wait 100-150ms + dputs("CH375 reset wait done"); + + 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(&ch375_dev); + + // Do not really set up the chip until it is used + ch375_set_usb_mode(&ch375_dev, CH375_USB_MODE_HOST_DISABLED); + ch375_enter_sleep(&ch375_dev); + + data.ready = false; + data.sectors = 0; + data.bytesPerSector = 0; + + // Build the template BPBs + drs->cmd.init.out.number_units = num_units; + for (i = 0; i < num_units; i++) { + fill_initial_bpb(&data.unit[i].bpb); + data.bpb_ptrs[i] = &data.unit[i].bpb; + } + drs->cmd.init.out.bpbs = data.bpb_ptrs; + drs->cmd.init.out.break_addr = MK_FP(get_cs(), FP_OFF(&resident_end)); + drs->status = DOS_DD_STATUS_DONE; + + 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; +fail: + drs->status = DOS_DD_STATUS_DONE | DOS_DD_STATUS_ERROR | DOS_DD_ERR_GENERAL_FAILURE; + drs->cmd.init.out.number_units = 0; + drs->cmd.init.out.break_addr = MK_FP(get_cs(), 0); // Deallocate everything + return; +} diff --git a/dos/chudisk.c b/dos/chudisk.c new file mode 100644 index 0000000..c7c0968 --- /dev/null +++ b/dos/chudisk.c @@ -0,0 +1,895 @@ +/* + * CHU - driver for CH375 in disk mode + * Copyright (C) 2024 Javier S. Pedro + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include +#include + +#include "inc/dosdrv.h" +#include "inc/bda.h" +#include "inc/mbr.h" + +#include "chudisk.h" +#include "dlog.h" +#include "utils.h" + +// Pre-declarations for the two driver routines, note there is a pragma aux for them +DOS_DD_STRATEGY strategy_routine; +DOS_DD_INTERRUPT interrupt_routine; + +/** This is the actual .SYS header! */ +const DOS_DD_HEADER __based(__segname("_CODE")) header = { + MK_FP(-1, -1), + DOS_DD_BLOCK | DOS_BLOCK_SUPPORT_32BIT_ADDRESSES + | DOS_BLOCK_FORMAT_NON_IBM | DOS_BLOCK_SUPPORT_REMOVABLE_MEDIA + | DOS_DD_SUPPORT_IOCTL_RW + | DOS_BLOCK_SUPPORT_IOCTL_GENERIC | DOS_DD_SUPPORT_IOCTL_QUERY, + strategy_routine, + interrupt_routine, + "\1CHUDISK" +}; + +/** Strategy routine saves DRS here for interrupt routine to use */ +DOS_DRS far * saved_drs; + +/** Device struct (used by ch375.h functions) */ +struct ch375 ch375_dev; + +// Some settings +/** Number of units handled by the driver (i.e. number of drive letters reserved. ) */ +unsigned char num_units; +/** Read-only mode (as if write-protected floppy) */ +bool read_only; +/** Process FAT32 partitions too. */ +bool show_fat32; + +/* Runtime data for the driver. */ +struct chudisk_data data; + +/** Buffer used to load boot sectors from disk and partitions. */ +char buffer[512]; + +/** Waits for the CH375 to generate an interrupt, returns result value. + * @param max_ticks max BIOS ticks to wait for interrupt. */ +uint8_t wait_for_ch375_result(unsigned int max_ticks) +{ + uint16_t last = bda_get_tick_count_lo(); + while (!ch375_poll_interrupt(&ch375_dev) && max_ticks > 0) { + uint16_t now = bda_get_tick_count_lo(); + if (now != last) { + last = now; + max_ticks--; + } + } + if (ch375_poll_interrupt(&ch375_dev)) { + uint8_t status = ch375_get_int_status(&ch375_dev); + return status; + } else { + dprintf("CH375 int timeout\n"); + return 0; + } +} + +/** Gets the disk sector from RW command DRS. */ +static inline uint32_t get_start_sector(const DOS_DRS far * drs) +{ + return (drs->cmd.rw.start_sector == 0xFFFF + ? drs->cmd.rw.start_sector_huge + : drs->cmd.rw.start_sector); +} + +static inline uint32_t chs_to_lba(const DOS_BPB * bpb, uint16_t cylinder, uint16_t head, uint16_t sector) +{ + uint16_t track = cylinder * bpb->numberOfHeads + head; + return mul(track, bpb->sectorsPerTrack) + (sector - 1); +} + +/** Read N sectors from the disk + * @return number of sectors read + */ +static unsigned disk_read_sectors(uint8_t far *buffer, uint32_t lba, uint8_t num) +{ + uint8_t result; + unsigned total = 0; // Total bytes written + + dprintf("disk read buf=%Fp sector=%lu num=%u\n", buffer, lba, num); + + // Initiate read operation + ch375_disk_read(&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) { + unsigned len; + + // Sanity check + if (total / SECTOR_SIZE >= num) { + dputs("read overflow"); + break; + } + + 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); + + if (len == 0) { + // Done? + break; + } + + total += len; + + // Continue + ch375_send_command(&ch375_dev, CH375_DISK_RD_GO); + } + + dprintf(" disk read total bytes = %u, sectors = %u, final status = 0x%hx\n", + total, total / SECTOR_SIZE, result); + + return total / SECTOR_SIZE; +} + +/** Write N sectors to the disk + * @return number of sectors written + */ +static unsigned disk_write_sectors(uint8_t far *buffer, uint32_t lba, uint8_t num) +{ + uint8_t result; + unsigned total = 0; // Total bytes written + + dprintf("disk write buf=%Fp sector=%lu num=%u\n", buffer, lba, num); + + // Initiate operation + ch375_disk_write(&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 = CH375_BLOCK_SIZE; + + // Sanity check + if (total / SECTOR_SIZE >= num) { + dputs("write overflow"); + break; + } + + 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_dev, CH375_DISK_WR_GO); + } + + dprintf(" disk write total bytes = %u, sectors = %u, final status = 0x%hx\n", + total, total / SECTOR_SIZE, result); + + return total / SECTOR_SIZE; +} + +/** Enables CH375 USB host mode, initializes "disk" mode on it + * and connects to a disk. */ +static bool disk_connect() +{ + int tries; + + dputs("switch to usb host"); + ch375_set_usb_mode(&ch375_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_dev); + if (wait_for_ch375_result(20) != CH375_INT_USB_SUCCESS) { + dputs("could not init disk"); + return false; + } + + // looks like we have to keep polling by asking "is the disk ready?" + // even if each time we ask we are already polling + tries = 3; + while (tries-- > 0) { + ch375_test_disk_ready(&ch375_dev); + if (wait_for_ch375_result(20) == CH375_INT_USB_SUCCESS) { + dputs("disk now init"); + return true; + } + bda_wait_tick(); + } + + dputs("disk failed to init"); + return false; +} + +/** Scans the disk for partitions. */ +static int disk_scan() +{ + uint32_t next_sector; + int num_parts, i; + + dprintf("disk scan show_fat32=%d\n", show_fat32); + + // Clear current partitions + for (i = 0; i < num_units; ++i) { + data.unit[i].exists = false; + data.unit[i].ready = false; + data.unit[i].start_sector = 0; + data.unit[i].sectors = 0; + } + + // Refresh disk size + ch375_send_command(&ch375_dev, CH375_DISK_SIZE); + if (wait_for_ch375_result(4) != CH375_INT_USB_SUCCESS) { + return DOS_DD_ERR_MEDIA_UNAVAILABLE; + } + + 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; + } + + data.sectors = SWAPNC_32(*(uint32_t*)&buffer[0]); + data.bytesPerSector = SWAPNC_32(*(uint32_t*)&buffer[4]); + + dprintf("sectors %lu bytesPerSector %lu\n", data.sectors, data.bytesPerSector); + if (data.bytesPerSector != SECTOR_SIZE) { + return DOS_DD_ERR_UNKNOWN_MEDIA; + } + + // Start creating partitions/units from partition tables + num_parts = 0; + + // Start with MBR at sector 0 + next_sector = 0; + do { + const MBR_PARTENTRY *partentry; + const uint32_t this_sector = next_sector; + + dprintf("reading boot record at sector %lu\n", this_sector); + + // Try to read sector + if (disk_read_sectors(buffer, this_sector, 1) != 1) { + // Fatal for MBR, non-fatal for EBR + if (this_sector == 0) return DOS_DD_ERR_READ_FAULT; + break; + } + + // Check for signature + if (*(uint16_t*)&buffer[510] != 0xAA55U) { + dprintf(" invalid MBR signature %x\n", *(uint16_t*)&buffer[510]); + if (this_sector == 0) return DOS_DD_ERR_UNKNOWN_MEDIA; + break; + } + + // Parse the MBR + partentry = (MBR_PARTENTRY*)&buffer[446]; + + next_sector = 0; // If there is another EBR we will store it here + for (i = 0; i < 4 && num_parts < MAX_UNITS; i++) { + dprintf(" partition %d type %x start %lu len %lu\n", i, partentry[i].type, partentry[i].lbaFirst, partentry[i].lbaLen); + + if (partentry[i].type == MBR_FAT12 || partentry[i].type == MBR_FAT16_32MB + || partentry[i].type == MBR_FAT16 || partentry[i].type == MBR_FAT16_LBA + || (show_fat32 && (partentry[i].type == MBR_FAT32 || partentry[i].type == MBR_FAT32_LBA)) ) { + data.unit[num_parts].exists = true; + data.unit[num_parts].start_sector = this_sector + partentry[i].lbaFirst; + data.unit[num_parts].sectors = partentry[i].lbaLen; + dprintf(" added as unit %u starts at %lu\n", num_parts, data.unit[num_parts].start_sector); + num_parts++; + } else if (partentry[i].type == MBR_EXT || partentry[i].type == MBR_EXT_LBA) { + // Go to the EBR next + next_sector = this_sector + partentry[i].lbaFirst; + } + } + } while (next_sector != 0 && num_parts < MAX_UNITS); + + // OK! + dprintf("found %u partitions\n", num_parts); + data.ready = true; + + return 0; +} + +static int disk_init() +{ + ch375_test_disk_ready(&ch375_dev); + if (wait_for_ch375_result(4) != CH375_INT_USB_SUCCESS) { + // Disk is not even connected, try to connect + if (!disk_connect()) { + // Nothing works: fail + return DOS_DD_ERR_DRIVE_NOT_READY; + } + } + // Disk is now ready: initiate scan for partitions + return disk_scan(); +} + +/** Return the volume label from the cached BPB, i.e. from the last time the drive was accessed. */ +static inline const char * unit_get_volume_label(uint8_t unit) +{ + const DOS_BPB *bpb = &data.unit[unit].bpb; + + if (bpb->sectorsPerFAT != 0) { + // FAT16 + return bpb->fat16.volumeLabel; + } else { + // FAT32 + return bpb->fat32.volumeLabel; + } +} + +/** Try to initialize logigcal drive 'unit'. This also initializes the entire disk + * if it was not done yet (scanning all partitions). + * It will read the BPB of the corresponding partition and cache it. */ +static int unit_init(uint8_t unit) +{ + DOS_BPB *bpb = &data.unit[unit].bpb; + int err; + + // Check if disk is ready, and initialize it if not + if (!data.ready) { + if (err = disk_init()) { + return err; + } + } + + // Disk should be ready, partitions enumerated + if (!data.unit[saved_drs->unit].exists) { + dputs("disk is ready, but partition does not exist"); + return DOS_DD_ERR_MEDIA_UNAVAILABLE; + } + + // Try to read starting sector of partition into buffer + if (disk_read_sectors(buffer, data.unit[unit].start_sector, 1) != 1) { + // Cannot read start sector... + return DOS_DD_ERR_READ_FAULT; + } + + // For now, copy largest possible BPB into our internal cache + memcpy(bpb, &buffer[DOS_BPB_OFFSET_ON_DISK], sizeof(DOS_BPB)); + + dprintf("unit %u BPB sectorsPerCluster=%u sectorsPerTrack=%u numberOfHeads=%u\n", + unit, bpb->sectorsPerCluster, + bpb->sectorsPerTrack, bpb->numberOfHeads); + dprintf(" bytesPerSector=%u numberOfFats=%u sectorsPerFat=%u \n", + bpb->bytesPerSector,bpb->numberOfFATs, + bpb->sectorsPerFAT); + dprintf(" numberOfSectors=%u numberOfSectorsLarge=%lu\n", + bpb->numberOfSectors, bpb->numberOfSectorsLarge); + + if (bpb->sectorsPerFAT != 0) { + // FAT12/16 + dprintf(" %s volumeSerialNumber=%lx volumeLabel='%.11s' fsType='%.8s'\n", + "FAT16", bpb->fat16.volumeSerialNumber, bpb->fat16.volumeLabel, bpb->fat16.fsType); + } else if (bpb->fat32.sectorPerFatLarge != 0) { + dprintf(" %s volumeSerialNumber=%lx volumeLabel='%.11s' fsType='%.8s'\n", + "FAT32", bpb->fat32.volumeSerialNumber, bpb->fat32.volumeLabel, bpb->fat32.fsType); + } else { + dprintf(" -> unknown filesystem\n"); + return DOS_DD_ERR_UNKNOWN_MEDIA; + } + + data.unit[saved_drs->unit].ready = true; + return 0; +} + +static bool unit_check_media_changed(uint8_t unit) +{ + if (data.ready) { + // Recheck if disk is still ready + ch375_test_disk_ready(&ch375_dev); + if (wait_for_ch375_result(4) == CH375_INT_USB_SUCCESS) { + // Disk is ready. But is partition ready ? + if (data.unit[unit].ready) { + // Disk ready, partition ready. Perfect! + dputs("mediaquery -> disk ready -> unit ready -> not changed"); + return false; + } else { + // Disk ready, but this partition is not. + // Likely first time access for this partition. + dputs("mediaquery -> disk ready -> unit !ready -> changed"); + return true; + } + } else { + // CH375 is not ready but we thought it was. USB has been removed! + dputs("mediaquery -> disk not ready -> changed"); + // Invalidate disk ready flag so that we rescan for partitions + data.ready = false; + return true; + } + } else { + // We do not think CH375 is ready. First time we check, or USB not present. + dputs("mediaquery -> !ready -> changed"); + return true; + } +} + +void strategy_routine(DOS_DRS __far *drs) +{ + load_ds_from_cs(); + //dprintf("strategy routine drs=%Fp len=%u func=%u\n", drs, drs->len, drs->function); + saved_drs = drs; +} + +void interrupt_routine() +{ + int err; + load_ds_from_cs(); + //dprintf("interrupt routine drs=%Fp len=%u func=%u\n", saved_drs, saved_drs->len, saved_drs->function); + switch (saved_drs->function) { + case DOS_DD_FUNC_INIT: + driver_init(saved_drs); + break; + case DOS_DD_FUNC_MEDIA_CHECK: + if (saved_drs->unit >= num_units) { + saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; + return; + } + saved_drs->cmd.mediacheck.out.result = unit_check_media_changed(saved_drs->unit) + ? DOS_DD_MEDIAQUERY_CHANGED : DOS_DD_MEDIAQUERY_NOT_CHANGED; + saved_drs->cmd.mediacheck.out.volume_id = unit_get_volume_label(saved_drs->unit); + saved_drs->status = DOS_DD_STATUS_DONE; + break; + case DOS_DD_FUNC_BUILD_BPB: + dprintf("build bpb unit=%hu\n", saved_drs->unit); + if (saved_drs->unit >= num_units) { + dputs("unit is unknown"); + saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; + return; + } + // Try to initialize disk and unit + if (err = unit_init(saved_drs->unit)) { + saved_drs->status = err | DOS_DD_STATUS_ERROR_DONE; + return; + } + // Return pointer to our cached BPB + saved_drs->cmd.buildbpb.out.bpb = &data.unit[saved_drs->unit].bpb; + saved_drs->status = DOS_DD_STATUS_DONE; + break; + case DOS_DD_FUNC_READ: + dprintf("read unit=%hu sector=%u large_sector=%lu len=%u\n", saved_drs->unit, + saved_drs->cmd.rw.start_sector, saved_drs->cmd.rw.start_sector_huge, saved_drs->cmd.rw.len); + if (saved_drs->unit >= num_units) { + saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; + return; + } + if (!data.ready || !data.unit[saved_drs->unit].ready) { + dputs("partition not ready"); + // We don't try to initialize the drive here, DOS must do that by calling buildBPB + saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; + return; + } + if (saved_drs->cmd.rw.len > 128) { + // Too many sectors at once! + // TODO: Should we allow partial reads? When I tried even "type " crashes. + // TODO: Should we also check for segment wraparound? + saved_drs->cmd.rw.len = 0; + saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE; + return; + } else if (saved_drs->cmd.rw.len >= 1) { + uint32_t sector = get_start_sector(saved_drs); + if (sector > data.unit[saved_drs->unit].sectors) { + saved_drs->cmd.rw.len = 0; + saved_drs->status = DOS_DD_ERR_SECTOR_NOT_FOUND | DOS_DD_STATUS_ERROR_DONE; + return; + } + // Make absolute + sector += data.unit[saved_drs->unit].start_sector; + if (sector > data.sectors) { + saved_drs->cmd.rw.len = 0; + saved_drs->status = DOS_DD_ERR_SEEK | DOS_DD_STATUS_ERROR_DONE; + return; + } + + saved_drs->cmd.rw.len = disk_read_sectors(saved_drs->cmd.rw.buffer, sector, saved_drs->cmd.rw.len); + if (saved_drs->cmd.rw.len == 0) { + // Not sure if required + saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE; + return; + } + } + saved_drs->cmd.rw.volume_id = unit_get_volume_label(saved_drs->unit); + saved_drs->status = DOS_DD_STATUS_DONE; + break; + case DOS_DD_FUNC_WRITE: + case DOS_DD_FUNC_WRITE_VERIFY: // TODO Not doing the verify yet part... + dprintf("write unit=%hu sector=%u large_sector=%lu len=%u\n", saved_drs->unit, + saved_drs->cmd.rw.start_sector, saved_drs->cmd.rw.start_sector_huge, saved_drs->cmd.rw.len); + if (saved_drs->unit >= num_units) { + saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; + return; + } + if (!data.ready || !data.unit[saved_drs->unit].ready) { + dputs("partition not ready"); + saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; + return; + } + if (read_only) { + saved_drs->status = DOS_DD_ERR_WRITE_PROTECT | DOS_DD_STATUS_ERROR_DONE; + return; + } + if (saved_drs->cmd.rw.len > 128) { + // Too much! + saved_drs->cmd.rw.len = 0; + saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE; + return; + } else if (saved_drs->cmd.rw.len >= 1) { + // Check relative sector value against partition limit + uint32_t sector = get_start_sector(saved_drs); + if (sector > data.unit[saved_drs->unit].sectors) { + saved_drs->cmd.rw.len = 0; + saved_drs->status = DOS_DD_ERR_SECTOR_NOT_FOUND | DOS_DD_STATUS_ERROR_DONE; + return; + } + // Make absolute and check against disk limit + sector += data.unit[saved_drs->unit].start_sector; + if (sector > data.sectors) { + saved_drs->cmd.rw.len = 0; + saved_drs->status = DOS_DD_ERR_SEEK | DOS_DD_STATUS_ERROR_DONE; + return; + } + + saved_drs->cmd.rw.len = disk_write_sectors(saved_drs->cmd.rw.buffer, sector, saved_drs->cmd.rw.len); + if (saved_drs->cmd.rw.len == 0) { + // Not sure if required + saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE; + return; + } + + if (saved_drs->function == DOS_DD_FUNC_WRITE_VERIFY) { + // Verify one sector at a time. Use our internal buffer. + uint8_t far * origbuf = saved_drs->cmd.rw.buffer; + unsigned i; + for (i = 0; i < saved_drs->cmd.rw.len; i++) { + dprintf(" verifying sector %u\n", i); + if (disk_read_sectors(buffer, sector + i, 1) != 1) { + saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE; + return; + } + + if (_fmemcmp(&origbuf[i * SECTOR_SIZE], buffer, SECTOR_SIZE) != 0) { + dprintf(" cmp error for sec %u\n", i); + saved_drs->status = DOS_DD_ERR_CRC | DOS_DD_STATUS_ERROR_DONE; + return; + } + } + } + } + saved_drs->cmd.rw.volume_id = unit_get_volume_label(saved_drs->unit); + saved_drs->status = DOS_DD_STATUS_DONE; + break; + case DOS_DD_FUNC_DEVICE_OPEN: + dputs("device open"); + // Do nothing here -- we init the device on build bpb, as with older DOSes + saved_drs->status = DOS_DD_STATUS_DONE; + break; + case DOS_DD_FUNC_DEVICE_CLOSE: + dputs("device close"); + saved_drs->status = DOS_DD_STATUS_DONE; + break; + case DOS_DD_FUNC_REMOVEABLE_MEDIA: + dputs("removable device"); + // If the disk is fixed, we must set "busy" bit here. We are removable. + saved_drs->status = DOS_DD_STATUS_DONE; + break; + case DOS_DD_FUNC_IOCTL_READ: + dprintf("ioctl read unit=%hu buf=%Fp len=%u\n", saved_drs->unit, + saved_drs->cmd.ioctlrw.buffer, saved_drs->cmd.ioctlrw.len); + if (saved_drs->unit >= num_units) { + saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; + return; + } + // If there's enough space, return our internal config string. + if (saved_drs->cmd.ioctlrw.len >= 3 + 2 + 1 + 5 + 1) { + uint8_t far * buf = saved_drs->cmd.ioctlrw.buffer; + int len = 0; + // B = base port (2 byte) + buf[len++] = 'B'; + _fmemcpy(&buf[len], &ch375_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; + // R = read only (1 byte) + buf[len++] = 'R'; + buf[len++] = read_only; + // S = partition start (4 bytes) + buf[len++] = 'S'; + _fmemcpy(&buf[len], &data.unit[saved_drs->unit].start_sector, sizeof(uint32_t)); + len += sizeof(uint32_t); + buf[len++] = '\0'; + saved_drs->cmd.ioctlrw.len = len; + } else { + saved_drs->cmd.ioctlrw.len = 0; + } + saved_drs->status = DOS_DD_STATUS_DONE; + break; + case DOS_DD_FUNC_IOCTL_WRITE: + dprintf("ioctl write unit=%hu buf=%Fp len=%u\n", saved_drs->unit, + saved_drs->cmd.ioctlrw.buffer, saved_drs->cmd.ioctlrw.len); + if (saved_drs->unit >= num_units) { + saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; + return; + } + if (saved_drs->cmd.ioctlrw.len >= 2) { + uint8_t far * buf = saved_drs->cmd.ioctlrw.buffer; + int i = 0; + do { + char c = buf[i++]; + switch (c) { + case 'S': // Partition start (5 bytes) + _fmemcpy(&data.unit[saved_drs->unit].start_sector, &buf[i], sizeof(uint32_t)); + i += sizeof(uint32_t); + dprintf(" unit=%hu new start=%lu\n", saved_drs->unit, data.unit[saved_drs->unit].start_sector); + // Mark unit as valid but needing refresh + data.unit[saved_drs->unit].exists = true; + data.unit[saved_drs->unit].ready = false; + break; + case 'L': // Partition len (5 bytes) + _fmemcpy(&data.unit[saved_drs->unit].sectors, &buf[i], sizeof(uint32_t)); + i += sizeof(uint32_t); + dprintf(" unit=%hu new len=%lu\n", saved_drs->unit, data.unit[saved_drs->unit].sectors); + // Mark unit as valid but needing refresh + data.unit[saved_drs->unit].exists = true; + data.unit[saved_drs->unit].ready = false; + break; + case '\0': + break; + default: + dprintf(" invalid control string char '%c' at %d\n", c, i-1); + saved_drs->cmd.ioctlrw.len = i; + saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE; + return; + } + } while (i < saved_drs->cmd.ioctlrw.len); + } else { + saved_drs->cmd.ioctlrw.len = 0; + saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE; + return; + } + saved_drs->status = DOS_DD_STATUS_DONE; + break; + case DOS_DD_FUNC_IOCTL_GENERIC: + dprintf("ioctl unit=%hu %x:%x buf=%Fp\n", saved_drs->unit, + saved_drs->cmd.ioctl.category, saved_drs->cmd.ioctl.minor, saved_drs->cmd.ioctl.data); + if (saved_drs->unit >= num_units) { + saved_drs->status = DOS_DD_ERR_UNKNOWN_UNIT | DOS_DD_STATUS_ERROR_DONE; + return; + } + // DOS can call these functions without doing a mediacheck/buildBPB + // Do not want to "forget" about a media change, so can't just call buildBPB here. + // But some functions (like get device parameters?) seem to act like a buildBPB. + if (unit_check_media_changed(saved_drs->unit)) { + // Init the disk, but not the partition, until we are sure we want to. + if (!data.ready) { + if (err = disk_init()) { + // Cannot access to disk or scan for partitions? No IOCTLs for you! + saved_drs->status = err | DOS_DD_STATUS_ERROR_DONE; + return; + } + } + } + if (saved_drs->cmd.ioctl.category == DOS_IOCTL_DISK) { + DOS_IOCTL_DISK_DATA far * iod = saved_drs->cmd.ioctl.data; + CHUDISK_IOCTL_DISK_DATA far * ciod = saved_drs->cmd.ioctl.data; + DOS_BPB * bpb = &data.unit[saved_drs->unit].bpb; + + switch (saved_drs->cmd.ioctl.minor) { + case DOS_IOCTL_DISK_GET_DEVICE_PARAMETERS: // Get CHS values (full disk!) and BPB + dprintf(" get_device_parameters specialFunctions=0x%x\n", iod->devparams.specialFunctions); + // If partition's BPB is not cached, read it from the disk + if (!data.unit[saved_drs->unit].ready) { + // This acts as if buildBPB was called + if ((err = unit_init(saved_drs->unit))) { + // TODO Now would be a good time to talk about a default BPB... + saved_drs->status = err | DOS_DD_STATUS_ERROR_DONE; + return; + } + } + + iod->devparams.specialFunctions = DOS_DEVICEPARAMS_USE_DEVICE_BPB | DOS_DEVICEPARAMS_REGULARTRACKS; + iod->devparams.deviceType = DOS_DEVICEPARAMS_HDD; + iod->devparams.deviceAttributes = DOS_DEVICEPARAMS_REMOVABLE; + if (bpb->numberOfHeads && bpb->sectorsPerTrack) { + // Compute number of cylinders of entire disk based on this part's BPB... + iod->devparams.numberOfCylinders = + div(data.sectors, bpb->numberOfHeads * bpb->sectorsPerTrack); + // TODO Check for overflows... + } else { + iod->devparams.numberOfCylinders = 0; + } + iod->devparams.mediaType = 0; + + // Return only DOS3.x BPB -- not sure if caller will have space for anything larger. + _fmemcpy(&iod->devparams.bpb, bpb, sizeof(DOS_BPB_DOS3)); + + dprintf(" get_device_parameters -> numberOfCylinders=%u\n", iod->devparams.numberOfCylinders); + + saved_drs->status = DOS_DD_STATUS_DONE; + return; + case DOS_IOCTL_DISK_GET_MEDIA_ID: // Get the volume label + if (!data.unit[saved_drs->unit].ready) { + // TODO Should we reload BPB here like above? + dputs(" but partition is not ready"); + saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; + return; + } + iod->mid.infoLevel = 0; + if (bpb->sectorsPerFAT != 0) { + iod->mid.serialNumber = bpb->fat16.volumeSerialNumber; + _fmemcpy(iod->mid.volumeLabel, bpb->fat16.volumeLabel, 11); + _fmemcpy(iod->mid.fsType, bpb->fat16.fsType, 8); + } else if (bpb->fat32.sectorPerFatLarge != 0) { + iod->mid.serialNumber = bpb->fat32.volumeSerialNumber; + _fmemcpy(iod->mid.volumeLabel, bpb->fat32.volumeLabel, 11); + _fmemcpy(iod->mid.fsType, bpb->fat32.fsType, 8); + } + dprintf(" get media id -> volumeSerialNumber=%lx volumeLabel='%.11Fs' fsType='%.8Fs'\n", + iod->mid.serialNumber, iod->mid.volumeLabel, iod->mid.fsType); + saved_drs->status = DOS_DD_STATUS_DONE; + return; + case DOS_IOCTL_DISK_SET_MEDIA_ID: // Set the volume label, etc. + if (!data.unit[saved_drs->unit].exists) { + dputs(" but partition does not exist!"); + saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; + return; + } + dprintf(" set media id to volumeSerialNumber=%lx volumeLabel='%.11Fs' fsType='%.8Fs'\n", + iod->mid.serialNumber, iod->mid.volumeLabel, iod->mid.fsType); + if (read_only) { + saved_drs->status = DOS_DD_ERR_WRITE_PROTECT | DOS_DD_STATUS_ERROR_DONE; + return; + } + // Here we always reload BPB -- do not want to write back outdated stuff. + if (disk_read_sectors(buffer, data.unit[saved_drs->unit].start_sector, 1) != 1) { + // Cannot read start sector... + saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE; + return; + } + bpb = (DOS_BPB*) &buffer[DOS_BPB_OFFSET_ON_DISK]; + if (bpb->sectorsPerFAT != 0) { + bpb->fat16.volumeSerialNumber = iod->mid.serialNumber; + _fmemcpy(bpb->fat16.volumeLabel, iod->mid.volumeLabel, 11); + _fmemcpy(bpb->fat16.fsType, iod->mid.fsType, 8); + } else if (bpb->fat32.sectorPerFatLarge != 0) { + bpb->fat32.volumeSerialNumber = iod->mid.serialNumber; + _fmemcpy(bpb->fat32.volumeLabel, iod->mid.volumeLabel, 11); + _fmemcpy(bpb->fat32.fsType, iod->mid.fsType, 8); + } else { + saved_drs->status = DOS_DD_ERR_UNKNOWN_MEDIA | DOS_DD_STATUS_ERROR_DONE; + return; + } + if (disk_write_sectors(buffer, data.unit[saved_drs->unit].start_sector, 1) != 1) { + // Cannot re-write start sector... + saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE; + return; + } + // Re-sync our internal BPB + memcpy(&data.unit[saved_drs->unit].bpb, bpb, sizeof(DOS_BPB)); + saved_drs->status = DOS_DD_STATUS_DONE; + return; + case DOS_IOCTL_DISK_READ_TRACK_ON_LOGICAL_DEVICE: // Absolute read + dprintf(" read track cylinder %u head %u sector %u sectors %u buf %Fp -> lba %lu\n", + iod->rw.cylinder, iod->rw.head, iod->rw.firstSector, iod->rw.sectors, iod->rw.buffer, + chs_to_lba(bpb, iod->rw.cylinder, iod->rw.head, iod->rw.firstSector + 1)); + // Need the partition's BPB to map CHS to LBA... + if (!data.unit[saved_drs->unit].ready) { + saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; + return; + } + if (disk_read_sectors(iod->rw.buffer, chs_to_lba(bpb, iod->rw.cylinder, iod->rw.head, iod->rw.firstSector + 1), iod->rw.sectors) != iod->rw.sectors) { + saved_drs->status = DOS_DD_ERR_READ_FAULT | DOS_DD_STATUS_ERROR_DONE; + return; + } + saved_drs->status = DOS_DD_STATUS_DONE; + return; + case DOS_IOCTL_DISK_WRITE_TRACK_ON_LOGICAL_DEVICE: + dprintf(" write track cylinder %u head %u sector %u sectors %u buf %Fp -> lba %lu\n", + iod->rw.cylinder, iod->rw.head, iod->rw.firstSector, iod->rw.sectors, iod->rw.buffer, + chs_to_lba(bpb, iod->rw.cylinder, iod->rw.head, iod->rw.firstSector + 1)); + // Need the partition's BPB to map CHS to LBA... + if (!data.unit[saved_drs->unit].ready) { + saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; + return; + } + if (read_only) { + saved_drs->status = DOS_DD_ERR_WRITE_PROTECT | DOS_DD_STATUS_ERROR_DONE; + return; + } + if (disk_write_sectors(iod->rw.buffer, chs_to_lba(bpb, iod->rw.cylinder, iod->rw.head, iod->rw.firstSector + 1), iod->rw.sectors) != iod->rw.sectors) { + saved_drs->status = DOS_DD_ERR_WRITE_FAULT | DOS_DD_STATUS_ERROR_DONE; + return; + } + saved_drs->status = DOS_DD_STATUS_DONE; + return; + case DOS_IOCTL_DISK_FORMAT_TRACK_ON_LOGICAL_DEVICE: + dprintf(" formatting track cylinder %u head %u\n", + iod->fv.cylinder, iod->fv.head); + if (!data.unit[saved_drs->unit].exists) { + dputs(" but partition does not exist!"); + saved_drs->status = DOS_DD_ERR_MEDIA_UNAVAILABLE | DOS_DD_STATUS_ERROR_DONE; + return; + } + if (read_only) { + saved_drs->status = DOS_DD_ERR_WRITE_PROTECT | DOS_DD_STATUS_ERROR_DONE; + return; + } + // Total nop. Should we zerofill the track or something? + iod->fv.specialFunctions = 1; // Not supported by BIOS? + saved_drs->status = DOS_DD_STATUS_DONE; + return; + case CHUDISK_IOCTL_DISK_READ_ABS_SECTOR: + dprintf(" read abs sector %lu sectors %u buf %Fp\n", + ciod->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; + } + saved_drs->status = DOS_DD_STATUS_DONE; + return; + } + } + + dputs(" ioctl not supported\n"); + saved_drs->status = DOS_DD_ERR_UNKNOWN_COMMAND | DOS_DD_STATUS_ERROR_DONE; + break; + case DOS_DD_FUNC_GET_LOGICAL_DEVICE: + dprintf("get logical device unit=%hu\n", saved_drs->unit); + saved_drs->unit = 0; + saved_drs->status = DOS_DD_STATUS_DONE; + break; + case DOS_DD_FUNC_SET_LOGICAL_DEVICE: + // We do not support this. Ignore it. + dprintf("set logical device unit=%hu\n", saved_drs->unit); + saved_drs->status = DOS_DD_STATUS_DONE; + break; + case DOS_DD_FUNC_IOCTL_QUERY: + dprintf("ioctl query %x:%x buf=%Fp\n", saved_drs->cmd.ioctl.category, saved_drs->cmd.ioctl.minor, saved_drs->cmd.ioctl.data); + switch (saved_drs->cmd.ioctl.category) { + case DOS_IOCTL_DISK: + switch (saved_drs->cmd.ioctl.minor) { + case DOS_IOCTL_DISK_GET_DEVICE_PARAMETERS: + case DOS_IOCTL_DISK_GET_MEDIA_ID: + case DOS_IOCTL_DISK_SET_MEDIA_ID: + case DOS_IOCTL_DISK_READ_TRACK_ON_LOGICAL_DEVICE: + case DOS_IOCTL_DISK_WRITE_TRACK_ON_LOGICAL_DEVICE: + case DOS_IOCTL_DISK_FORMAT_TRACK_ON_LOGICAL_DEVICE: + case CHUDISK_IOCTL_DISK_READ_ABS_SECTOR: + // Supported + saved_drs->status = DOS_DD_STATUS_DONE; + return; + } + break; + } + // Unsupported + saved_drs->status = DOS_DD_ERR_UNKNOWN_COMMAND | DOS_DD_STATUS_ERROR_DONE; + break; + default: + dprintf("unknown function received %u\n", saved_drs->function); + break; + } +} + +// Required by Watcom ABI -- since we do not link to the runtime +void _small_code() {} + +// Symbol to let initialization code know the end of resident data +char resident_end; diff --git a/dos/chudisk.h b/dos/chudisk.h new file mode 100644 index 0000000..1d2fbe4 --- /dev/null +++ b/dos/chudisk.h @@ -0,0 +1,93 @@ +/* + * CHU - driver for CH375 in disk mode + * Copyright (C) 2024 Javier S. Pedro + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CHUDISK_H +#define CHUDISK_H + +#include + +#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 +extern struct ch375 ch375_dev; + +extern unsigned char num_units; + +extern bool read_only; + +extern bool show_fat32; + +/** Runtime data */ +extern struct chudisk_data +{ + /** Pointers to all BPBs, to return to DOS during init. */ + DOS_BPB * bpb_ptrs[MAX_UNITS]; + + /** 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 { + /** Whether the partition exists (start_sector points to it) */ + bool exists; + /** Note in theory may be zero (superfloppy) */ + uint32_t start_sector; + /** Number of sectors for this partition. */ + uint32_t sectors; + /** Whether the partition's BPB was loaded. */ + bool ready; + /** DOS device params (contains BPB), only valid if 'ready'. */ + DOS_BPB bpb; + } unit[MAX_UNITS]; +} data; + +// Functions called by both init part and resident part +extern void driver_init(DOS_DRS __far *drs); + +extern char resident_end; + +extern uint8_t wait_for_ch375_result(unsigned int max_ticks); + + +// CHUDISK IOCTLs +// These are used with the DISK major +// TODO Not sure we can really happily add custom minors here, +// but then again DOS outright refuses to accept any other major +enum CHUDISK_IOCTL_DISK_MINOR { + CHUDISK_IOCTL_DISK_READ_ABS_SECTOR = 0xC1 +}; + +typedef _Packed union CHUDISK_IOCTL_DISK_DATA +{ + struct CHUDISK_RWABSSECTOR { + uint32_t sector; + uint8_t sectors; + void far * buffer; + } rwabs; +} CHUDISK_IOCTL_DISK_DATA; + +#endif // CHUDISK_H diff --git a/dos/chudisk.lnk b/dos/chudisk.lnk new file mode 100644 index 0000000..1768998 --- /dev/null +++ b/dos/chudisk.lnk @@ -0,0 +1,4 @@ +format dos com + +option statics +option map diff --git a/dos/cio.c b/dos/cio.c new file mode 100644 index 0000000..43a2a27 --- /dev/null +++ b/dos/cio.c @@ -0,0 +1,147 @@ +/* + * CHU - driver for CH375 in disk mode + * Copyright (C) 2024 Javier S. Pedro + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Simple printf function for console, a bit wasteful for what it is used. + * But it is part of the init code, so it will be thrown away. + * dlog.h is more powerful but only in debug builds. */ + + +#include +#include +#include +#include + +#include "inc/dosapi.h" + +#include "cio.h" + +static inline void c_llputc(char c) +{ + dos_print_char(c); +} + +void c_utoa(unsigned num, unsigned base) +{ + char buf[6]; + int i = 0; + + do { + unsigned digit = num % base; + + if (digit < 10) { + buf[i] = '0' + digit; + } else { + buf[i] = 'a' + (digit - 10); + } + + i++; + num /= base; + } while (num > 0 && i < sizeof(buf)); + + while (i--) { + c_llputc(buf[i]); + } +} + +void c_itoa(int num, unsigned base) +{ + unsigned unum; + + if (num < 0) { + c_llputc('-'); + unum = -num; + } else { + unum = num; + } + + c_utoa(unum, base); +} + +unsigned c_strtou(const char * *str) +{ + unsigned i = 0; + const char *s = *str; + char c = *s; + while (c >= '0' && c <= '9') { + i = (i * 10) + (c - '0'); + c = *(++s); + } + *str = s; + return i; +} + +static void c_printstr(const char *s) +{ + while (*s) { c_llputc(*s++); } +} + +void c_printf(const char *fmt, ...) +{ + va_list va; + char c; + + va_start(va, fmt); + + while ((c = *(fmt++))) { + if (c == '%') { + switch ((c = *(fmt++))) { + case 'd': + c_itoa(va_arg(va, int), 10); + break; + case 'u': + c_utoa(va_arg(va, unsigned), 10); + break; + case 'x': + case 'p': + c_utoa(va_arg(va, unsigned), 16); + break; + case 's': + c_printstr(va_arg(va, const char *)); + break; + case 'c': + c_putc(va_arg(va, char)); + break; + case '%': + c_llputc('%'); + break; + } + } else { + c_putc(c); + } + } + + va_end(va); +} + +void c_putc(char c) +{ + if (c == '\n') { + c_llputc('\r'); + } + c_llputc(c); +} + + +void c_puts(const char *s) +{ + char c; + while ((c = *(s++))) { + c_putc(c); + } + c_putc('\n'); +} diff --git a/dos/cio.h b/dos/cio.h new file mode 100644 index 0000000..24a67ae --- /dev/null +++ b/dos/cio.h @@ -0,0 +1,31 @@ +/* + * CHU - driver for CH375 in disk mode + * Copyright (C) 2024 Javier S. Pedro + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#ifndef CIO_H +#define CIO_H + +void c_utoa(unsigned num, unsigned base); +void c_itoa(int num, unsigned base); +unsigned c_strtou(const char * *str); + +void c_printf(const char *fmt, ...); +void c_putc(char c); +void c_puts(const char *s); + +#endif // CIO_H diff --git a/dos/inc/bda.h b/dos/inc/bda.h new file mode 100644 index 0000000..b447221 --- /dev/null +++ b/dos/inc/bda.h @@ -0,0 +1,67 @@ +/* + * VBMouse - BIOS data area access routines + * Copyright (C) 2022 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef BDA_H +#define BDA_H + +#define BIOS_DATA_AREA_SEGMENT 0x40 + +static inline uint32_t bda_get_dword(unsigned int offset) { + uint32_t __far *p = MK_FP(BIOS_DATA_AREA_SEGMENT, offset); + return *p; +} + +static inline uint16_t bda_get_word(unsigned int offset) { + uint16_t __far *p = MK_FP(BIOS_DATA_AREA_SEGMENT, offset); + return *p; +} + +static inline uint8_t bda_get_byte(unsigned int offset) { + uint8_t __far *p = MK_FP(BIOS_DATA_AREA_SEGMENT, offset); + return *p; +} + +#define bda_get_ebda_segment() bda_get_word(0x0e) + +#define bda_get_equipment() bda_get_word(0x10) + +#define bda_get_video_mode() bda_get_byte(0x49) +#define bda_get_num_columns() bda_get_word(0x4a) +#define bda_get_video_page_size() bda_get_word(0x4c) +#define bda_get_cur_video_page() bda_get_word(0x62) +#define bda_get_last_row() bda_get_byte(0x84) +#define bda_get_char_height() bda_get_word(0x85) + +#define bda_get_tick_count() bda_get_dword(0x6c) +#define bda_get_tick_count_lo() bda_get_word(0x6c) + +static inline void bda_wait_tick() { + uint16_t cur_ticks = bda_get_tick_count_lo(); + do { + //pause(); + } while (cur_ticks == bda_get_tick_count_lo()); +} + +static inline void bda_wait_ticks(unsigned int ticks) { + for (; ticks > 0; ticks--) { + bda_wait_tick(); + } +} + +#endif // BDA_H diff --git a/dos/inc/bpb.h b/dos/inc/bpb.h new file mode 100644 index 0000000..8df04a1 --- /dev/null +++ b/dos/inc/bpb.h @@ -0,0 +1,83 @@ +#ifndef BPB_H +#define BPB_H + +#include +#include "sassert.h" + +#define DOS_BPB_OFFSET_ON_DISK 0x0B + +typedef _Packed struct DOS_BPB_DOS3 +{ + /** Number of bytes per sector. */ + uint16_t bytesPerSector; + /** Number of sectors per cluster. Must be power of 2. */ + uint8_t sectorsPerCluster; + /** Number of reserved sectors. */ + uint16_t reservedSectors; + /** Number of FATs. */ + uint8_t numberOfFATs; + /** Maximum number of entries in the root directory. */ + uint16_t rootDirEntries; + /** Total number of sectors on the drive. If drive larger than 32MB, this field is zero, see numberOfSectorsLarge. */ + uint16_t numberOfSectors; + /** Media descriptor byte. */ + uint8_t mediaType; + /** Number of sectors used by a FAT. */ + uint16_t sectorsPerFAT; + /** Number of sectors per track. */ + uint16_t sectorsPerTrack; + /** Number of heads. */ + uint16_t numberOfHeads; + /** Number of hidden sectors on the drive, from the start of the drive. */ + uint32_t hiddenSectors; + /** Number of sectors, only used if drive > 32MB. */ + uint32_t numberOfSectorsLarge; +} DOS_BPB_DOS3; +STATIC_ASSERT(sizeof(DOS_BPB_DOS3) == 25); + +typedef _Packed struct DOS_BPB_DOS4 +{ + struct DOS_BPB_DOS3; + uint8_t physicalDriveNumber; + uint8_t flags; + uint8_t extendedBootSignature; + uint32_t volumeSerialNumber; + char volumeLabel[11]; + char fsType[8]; +} DOS_BPB_DOS4; +STATIC_ASSERT(sizeof(DOS_BPB_DOS4) == 25 + 26); + +typedef _Packed struct DOS_BPB_FAT32 +{ + struct DOS_BPB_DOS3; + uint32_t sectorPerFatLarge; + uint16_t extendedFlags; + uint16_t version; + uint32_t rootDirCluster; + uint16_t infoSector; + uint16_t backupBootSector; + + char bootFileName[12]; + uint8_t physicalDriveNumber; + uint8_t flags; + uint8_t extendedBootSignature; + uint32_t volumeSerialNumber; + char volumeLabel[11]; + char fsType[8]; +} DOS_BPB_FAT32; +STATIC_ASSERT(sizeof(DOS_BPB_FAT32) == 25 + 54); + +/** Union of all BPB types. Default is DOS4 / FAT16. */ +typedef union DOS_BPB +{ + struct DOS_BPB_DOS3; + struct DOS_BPB_DOS4 fat16; + struct DOS_BPB_FAT32 fat32; +} DOS_BPB; + +typedef enum DOS_BPB_MEDIA_TYPE +{ + DOS_BPB_MEDIA_HDD = 0xF8 +} DOS_BPB_MEDIA_TYPE; + +#endif // BPB_H diff --git a/dos/inc/dosapi.h b/dos/inc/dosapi.h new file mode 100644 index 0000000..18dbde0 --- /dev/null +++ b/dos/inc/dosapi.h @@ -0,0 +1,182 @@ +/* + * VBMouse - Interface to some DOS int 21h services + * Copyright (C) 2022 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef INT21DOS_H +#define INT21DOS_H + +#include +#include +#include +#include +#include "sassert.h" + +#define DOS_PSP_SIZE 256 + +enum dos_error { + DOS_ERROR_SUCCESS = 0, + DOS_ERROR_INVALID_FUNCTION = 1, + DOS_ERROR_FILE_NOT_FOUND = 2, + DOS_ERROR_PATH_NOT_FOUND = 3, + DOS_ERROR_TOO_MANY_OPEN_FILES = 4, + DOS_ERROR_ACCESS_DENIED = 5, + DOS_ERROR_INVALID_HANDLE = 6, + DOS_ERROR_ARENA_TRASHED = 7, + DOS_ERROR_NOT_ENOUGH_MEMORY = 8, + DOS_ERROR_INVALID_BLOCK = 9, + DOS_ERROR_BAD_ENVIRONMENT = 10, + DOS_ERROR_BAD_FORMAT = 11, + DOS_ERROR_INVALID_ACCESS = 12, + DOS_ERROR_INVALID_DATA = 13, + DOS_ERROR_OUT_OF_MEMORY = 14, + DOS_ERROR_INVALID_DRIVE = 15, + DOS_ERROR_CURRENT_DIRECTORY = 16, + DOS_ERROR_NOT_SAME_DEVICE = 17, + DOS_ERROR_NO_MORE_FILES = 18, + DOS_ERROR_WRITE_PROTECT = 19, + DOS_ERROR_BAD_UNIT = 20, + DOS_ERROR_NOT_READY = 21, + DOS_ERROR_BAD_COMMAND = 22, + DOS_ERROR_CRC = 23, + DOS_ERROR_BAD_LENGTH = 24, + DOS_ERROR_SEEK = 25, + DOS_ERROR_NOT_DOS_DISK = 26, + DOS_ERROR_SECTOR_NOT_FOUND = 27, + + DOS_ERROR_GEN_FAILURE = 31, + + DOS_ERROR_HANDLE_EOF = 38, + DOS_ERROR_HANDLE_DISK_FULL = 39, + + DOS_ERROR_FILE_EXISTS = 80, + DOS_ERROR_CANNOT_MAKE = 82, +}; + +// General functions + +static inline void dos_print_char(char c); +#pragma aux dos_print_char = \ + "mov ah, 0x02" \ + "int 0x21" \ + __parm [dl] \ + __modify [ah] + +static inline uint8_t dos_read_char(void); +#pragma aux dos_read_char = \ + "mov ah, 0x08" \ + "int 0x21" \ + __value [al] \ + __modify [ax] + +static inline void dos_print_string(const char *s); +#pragma aux dos_print_string = \ + "mov ah, 0x09" \ + "int 0x21" \ + __parm [dx] \ + __modify [ah] + +static inline uint16_t dos_get_version(void); +#pragma aux dos_get_version = \ + "mov ah, 0x30" \ + "int 0x21" \ + __value [ax] \ + __modify [bx cx] + +// Memory manipulation functions + +typedef __segment segment_t; + +enum dos_allocation_strategy { + DOS_FIT_FIRST = 0, + DOS_FIT_BEST = 1, + DOS_FIT_LAST = 2, + + DOS_FIT_HIGH = 0x80, + DOS_FIT_HIGHONLY = 0x40, +}; + +/** Converts bytes to paragraphs (16 bytes), rounding up. */ +static inline unsigned get_paragraphs(unsigned bytes) +{ + return (bytes + 15) / 16; +} + +static unsigned dos_query_allocation_strategy(void); +#pragma aux dos_query_allocation_strategy = \ + "mov ax, 0x5800" \ + "int 0x21" \ + __value [ax] + +static int dos_set_allocation_strategy(unsigned strategy); +#pragma aux dos_set_allocation_strategy = \ + "clc" \ + "mov ax, 0x5801" \ + "int 0x21" \ + "jc end" \ + "mov ax, 0" \ + "end:" \ + __parm [bx] \ + __value [ax] + +static bool dos_query_umb_link_state(void); +#pragma aux dos_query_umb_link_state = \ + "mov ax, 0x5802" \ + "int 0x21" \ + __value [al] + +static int dos_set_umb_link_state(bool state); +#pragma aux dos_set_umb_link_state = \ + "clc" \ + "mov ax, 0x5803" \ + "int 0x21" \ + "jc end" \ + "mov ax, 0" \ + "end:" \ + __parm [bx] \ + __value [ax] + +/** Allocates a new DOS segment. + * @returns either the allocated segment or 0 if any error. */ +static __segment dos_alloc(unsigned paragraphs); +#pragma aux dos_alloc = \ + "clc" \ + "mov ah, 0x48" \ + "int 0x21" \ + "jnc end" \ + "mov ax, 0xB" \ + "end:" \ + __parm [bx] \ + __value [ax] + +/** Frees DOS segment. */ +static void dos_free(segment_t segment); +#pragma aux dos_free = \ + "mov ah, 0x49" \ + "int 0x21" \ + __parm [es] \ + __modify [ax] + +/** Sets a given PSP as the current active process. */ +static void dos_set_psp(segment_t psp); +#pragma aux dos_set_psp = \ + "mov ah, 0x50" \ + "int 0x21" \ + __parm [bx] \ + __modify [ax] + +#endif // INT21DOS_H diff --git a/dos/inc/dosdrv.h b/dos/inc/dosdrv.h new file mode 100644 index 0000000..0c33c54 --- /dev/null +++ b/dos/inc/dosdrv.h @@ -0,0 +1,319 @@ +#ifndef DOSDRV_H +#define DOSDRV_H + +#include +#include "bpb.h" +#include "sassert.h" + +typedef enum DOS_DD_ERROR +{ + DOS_DD_ERR_WRITE_PROTECT = 0, + DOS_DD_ERR_UNKNOWN_UNIT = 1, + DOS_DD_ERR_DRIVE_NOT_READY = 2, + DOS_DD_ERR_UNKNOWN_COMMAND = 3, + DOS_DD_ERR_CRC = 4, + DOS_DD_ERR_BAD_DRS_LEN = 5, + DOS_DD_ERR_SEEK = 6, + DOS_DD_ERR_UNKNOWN_MEDIA = 7, + DOS_DD_ERR_SECTOR_NOT_FOUND = 8, + DOS_DD_ERR_PRINTER_OUT_OF_PAPER = 9, + DOS_DD_ERR_WRITE_FAULT = 10, + DOS_DD_ERR_READ_FAULT = 11, + DOS_DD_ERR_GENERAL_FAILURE = 12, + DOS_DD_ERR_MEDIA_UNAVAILABLE = 0xE, + DOS_DD_ERR_INVALID_DISK_CHANGE = 0xF +} DOS_DD_ERROR; + +typedef enum DOS_DD_STATUS { + DOS_DD_STATUS_DONE = 1 << 8, + DOS_DD_STATUS_BUSY = 1 << 9, + DOS_DD_STATUS_ERROR = 1 << 15, + + DOS_DD_STATUS_ERROR_DONE = DOS_DD_STATUS_ERROR | DOS_DD_STATUS_DONE +} DOS_DD_STATUS; + +typedef enum DOS_DD_MEDIAQUERY_RESULT { + DOS_DD_MEDIAQUERY_CHANGED = -1, + DOS_DD_MEDIAQUERY_UNKNOWN = 0, + DOS_DD_MEDIAQUERY_NOT_CHANGED = 1 +} DOS_DD_MEDIAQUERY_RESULT; + +typedef _Packed struct DOS_DRS +{ + /** The length of this structure. */ + uint8_t len; + /** The subunit the operation is for. */ + uint8_t unit; + + uint8_t function; + + uint16_t status; + + uint32_t reserved1; + uint32_t reserved2; + + union { + union { + struct { + uint8_t reserved; + /** End of memory available for driver. */ + void __far * avail_addr; + /** Command line (i.e. the DEVICE= line used to load this driver. ) */ + const char __far * cmdline; + /** First drive that is going to be allocated for this driver. */ + uint8_t first_drive_number; + } in; + struct { + /** Number of units/drive letters to allocate for this driver. */ + uint8_t number_units; + /** Break addr: everything after this address is deallocated after init. */ + void __far * break_addr; + /** Pointer to the BPB array. */ + DOS_BPB * __far * bpbs; + uint8_t reserved; + /** Display error message on failure. */ + uint16_t error_msg_flag; + } out; + } init; + union { + struct { + /** Media descriptor of the current media. */ + uint8_t cur_media_desc; + } in; + struct { + uint8_t reserved; + uint8_t result; + /** Volume ID of previous loaded media. */ + char far * volume_id; + } out; + } mediacheck; + union { + struct { + /** Media descriptor of the current media. */ + uint8_t cur_media_desc; + /** Buffer with sector 0 of the disk, depending on driver flags */ + void __far * sector_zero; + } in; + struct { + uint8_t reserved; + void __far * sector_zero; + /** Address for the new BPB. */ + DOS_BPB __far * bpb; + } out; + } buildbpb; + struct { + uint8_t data; + /** Input: pointer to buffer. */ + void __far * buffer; + /** Input/Output: buffer len / actual len */ + uint16_t len; + } ioctlrw; + struct { + /** Input: current media descriptor byte. */ + uint8_t cur_media_desc; + /** Input: pointer to buffer. */ + void __far * buffer; + /** Input: Number of sectors to read/write. Output: number sectors actually read/written. */ + uint16_t len; + /** Input: Begin sector. Or, if 0xFFFF, see start_sector_huge. */ + uint16_t start_sector; + /** Output: Current Volume ID. */ + char far * volume_id; + uint32_t start_sector_huge; + } rw; + struct { + uint8_t category; + uint8_t minor; + uint32_t reserved; + void __far * data; + } ioctl; + } cmd; +} DOS_DRS; + +typedef void (DOS_DD_STRATEGY)(DOS_DRS __far *drs); +typedef void (DOS_DD_INTERRUPT)(DOS_DRS __far *drs); +#pragma aux DOS_DD_STRATEGY __parm __caller [es bx] __modify [] __loadds __far +#pragma aux DOS_DD_INTERRUPT __parm __caller [] __modify [] __loadds __far + +typedef enum DOS_DD_FUNCTIONS +{ + DOS_DD_FUNC_INIT = 0, + DOS_DD_FUNC_MEDIA_CHECK = 1, + DOS_DD_FUNC_BUILD_BPB = 2, + DOS_DD_FUNC_IOCTL_READ = 3, + DOS_DD_FUNC_READ = 4, + DOS_DD_FUNC_NONDESTRUCTIVE_READ = 5, + DOS_DD_FUNC_INPUT_STATUS = 6, + DOS_DD_FUNC_INPUT_FLUSH = 7, + DOS_DD_FUNC_WRITE = 8, + DOS_DD_FUNC_WRITE_VERIFY = 9, + DOS_DD_FUNC_OUTPUT_STATUS = 10, + DOS_DD_FUNC_OUTPUT_FLUSH = 11, + DOS_DD_FUNC_IOCTL_WRITE = 12, + DOS_DD_FUNC_DEVICE_OPEN = 13, + DOS_DD_FUNC_DEVICE_CLOSE = 14, + DOS_DD_FUNC_REMOVEABLE_MEDIA = 15, + DOS_DD_FUNC_OUTPUT_UNTIL_BUSY = 16, + DOS_DD_FUNC_IOCTL_GENERIC = 19, + DOS_DD_FUNC_GET_LOGICAL_DEVICE = 23, + DOS_DD_FUNC_SET_LOGICAL_DEVICE = 24, + DOS_DD_FUNC_IOCTL_QUERY = 25, +} DOS_DD_COMMAND; + + +/** Device driver (.sys) header */ +typedef _Packed struct DOS_DD_HEADER +{ + void __far *nextDevice; + uint16_t attributes; + DOS_DD_STRATEGY *strategyCb; + DOS_DD_INTERRUPT *interrupCb; + char name[8]; +} DOS_DD_HEADER; + +typedef enum DOS_DD_ATTRIBUTES +{ + /** Character-device driver. */ + DOS_DD_CHAR = 1 << 15, + /** Block-device driver. */ + DOS_DD_BLOCK = 0, + + /** Supports IOCTL Read/Write funcs */ + DOS_DD_SUPPORT_IOCTL_RW = 1 << 14, + + /** Support IOCTL query funcs. */ + DOS_DD_SUPPORT_IOCTL_QUERY = 1 << 7, + + // Character-device specific + DOS_CHAR_CURRENT_STDIN_DEVICE = 1 << 0, + DOS_CHAR_CURRENT_STDOUT_DEVICE = 1 << 1, + DOS_CHAR_CURRENT_NUL_DEVICE = 1 << 2, + DOS_CHAR_CURRENT_CLOCK_DEVICE = 1 << 3, + DOS_CHAR_SPECIAL_FAST_CONSOLE_IO = 1 << 4, + DOS_CHAR_OUTPUT_UNTIL_BUSY = 1 << 13, + + // Block-device specific + /** The driver support 32-bit sector address. (Can use the _huge fields. ) */ + DOS_BLOCK_SUPPORT_32BIT_ADDRESSES = 1 << 1, + + /** Support Get/Set Logical Drive and generic IOCTL funcs. */ + DOS_BLOCK_SUPPORT_IOCTL_GENERIC = 1 << 6, + + /** Supports Open/Close/Removable media funcs */ + DOS_BLOCK_SUPPORT_REMOVABLE_MEDIA = 1 << 11, + + /** if set, DOS not read the 1st sector of the 1st FAT during build bpb. */ + DOS_BLOCK_FORMAT_NON_IBM = 1 << 13, +} DOS_DD_ATTRIBUTES; + +// Generic IOCTLs + +typedef enum DOS_IOCTL_CATEGORY +{ + DOS_IOCTL_SERIAL = 0x1, + DOS_IOCTL_CONSOLE = 0x3, + DOS_IOCTL_PARALLEL = 0x5, + DOS_IOCTL_DISK = 0x8, +} DOS_IOCTL_CATEGORY; + +typedef enum DOS_IOCTL_MINOR +{ + DOS_IOCTL_CONSOLE_SET_ITERATION_COUNT = 0x45, + DOS_IOCTL_CONSOLE_SELECT_CODE_PAGE = 0x4A, + DOS_IOCTL_CONSOLE_START_CODE_PAGE_PREPARE = 0x4C, + DOS_IOCTL_CONSOLE_END_CODE_PAGE_PREPARE = 0x4D, + DOS_IOCTL_CONSOLE_GET_IERATION_COUNT = 0x65, + DOS_IOCTL_CONSOLE_QUERY_SELECTED_CODE_PAGE = 0x6A, + DOS_IOCTL_CONSOLE_QUERY_CODE_PAGE_PREPARE_LIST = 0x6B, + + DOS_IOCTL_DISK_SET_DEVICE_PARAMETERS = 0x40, + DOS_IOCTL_DISK_WRITE_TRACK_ON_LOGICAL_DEVICE = 0x41, + DOS_IOCTL_DISK_FORMAT_TRACK_ON_LOGICAL_DEVICE = 0x42, + DOS_IOCTL_DISK_SET_MEDIA_ID = 0x46, + DOS_IOCTL_DISK_GET_DEVICE_PARAMETERS = 0x60, + DOS_IOCTL_DISK_READ_TRACK_ON_LOGICAL_DEVICE = 0x61, + DOS_IOCTL_DISK_VERIFY_TRACK_ON_LOGICAL_DEVICE = 0x62, + DOS_IOCTL_DISK_GET_MEDIA_ID = 0x66, + DOS_IOCTL_DISK_SENSE_MEDIA_TYPE = 0x68, +} DOS_IOCTL_MINOR; + + +// Block-device / disk data structures + +typedef _Packed struct DOS_DEVICEPARAMS +{ + uint8_t specialFunctions; + uint8_t deviceType; + uint16_t deviceAttributes; + uint16_t numberOfCylinders; + uint8_t mediaType; + DOS_BPB_DOS3 bpb; +} DOS_DEVICEPARAMS; + +enum DOS_DEVICEPARAMS_SPECIAL_FUNCTIONS +{ + /** Use the device BPB for all subsequent Build BPB requests. */ + DOS_DEVICEPARAMS_USE_DEVICE_BPB = (1 << 0), + /** Ignore all fields except the TRACKLAYOUT structure appended after DEVICEPARAMS */ + DOS_DEVICEPARAMS_TRACKLAYOUT = (1 << 1), + /** The sectors in the track are all the same size, and sector numbers are in the range [1..N] + * where N is the total number of sectors in the track. */ + DOS_DEVICEPARAMS_REGULARTRACKS = (1 << 2), +}; + +enum DOS_DEVICEPARAMS_DEVICE_TYPE +{ + DOS_DEVICEPARAMS_320_360K = 0x0, + DOS_DEVICEPARAMS_1_2_MB = 0x1, + DOS_DEVICEPARAMS_720K = 0x2, + DOS_DEVICEPARAMS_8INCH_SD = 0x3, + DOS_DEVICEPARAMS_8INCH_DD = 0x4, + DOS_DEVICEPARAMS_HDD = 0x5, + DOS_DEVICEPARAMS_TAPE = 0x6, + DOS_DEVICEPARAMS_1_44_MB = 0x7, + DOS_DEVICEPARAMS_OPTICAL = 0x8, + DOS_DEVICEPARAMS_2_88_MB = 0x9 +}; + +enum DOS_DEVICEPARAMS_DEVICE_ATTR +{ + DOS_DEVICEPARAMS_REMOVABLE = 0, + DOS_DEVICEPARAMS_FIXED = 1, + DOS_DEVICEPARAMS_HAS_DOORLOCK = 1 << 1 +}; + +typedef _Packed struct DOS_MEDIA_ID +{ + /** Must be zero. */ + uint16_t infoLevel; + uint32_t serialNumber; + char volumeLabel[11]; + char fsType[8]; +} DOS_MEDIA_ID; + +typedef _Packed struct DOS_RWBLOCK +{ + uint8_t specialFunctions; + uint16_t head; + uint16_t cylinder; + uint16_t firstSector; + uint16_t sectors; + void far * buffer; +} DOS_RWBLOCK; + +typedef _Packed struct DOS_FVBLOCK +{ + uint8_t specialFunctions; + uint16_t head; + uint16_t cylinder; +} DOS_FVBLOCK; + +typedef _Packed union DOS_IOCTL_DISK_DATA +{ + DOS_DEVICEPARAMS devparams; + DOS_MEDIA_ID mid; + DOS_FVBLOCK fv; + DOS_RWBLOCK rw; +} DOS_IOCTL_DISK_DATA; + +#endif // DOSSYS_H diff --git a/dos/inc/dosint.h b/dos/inc/dosint.h new file mode 100644 index 0000000..cfa855d --- /dev/null +++ b/dos/inc/dosint.h @@ -0,0 +1,250 @@ +#ifndef DOSINT_H +#define DOSINT_H + +// Internal DOS structures + +// For documentation of these, better grab "Undocumented DOS" or similar book +// These are probably valid only on DOS >= 4 + +typedef _Packed struct dos_current_directory_structure { + char curr_path[67]; + uint16_t flags; + uint32_t disk_blk; + void __far * info; + char pad1[11]; +} DOSCDS; +STATIC_ASSERT(sizeof(DOSCDS) == 88); + +enum dos_cds_flags { + DOS_CDS_FLAG_PHYSICAL = 0x4000, + DOS_CDS_FLAG_NETWORK = 0x8000 +}; + +typedef _Packed struct dos_system_file_table_entry { + uint16_t num_handles; + uint16_t open_mode; + uint8_t attr; + uint16_t dev_info; + void __far *dpb; + uint16_t start_cluster; + uint16_t f_time; + uint16_t f_date; + uint32_t f_size; + uint32_t f_pos; + uint16_t last_rel_cluster; + uint16_t last_abs_cluster; + uint16_t dir_sector; + uint8_t dir_entry; + char filename[11]; +} DOSSFT; +STATIC_ASSERT(sizeof(DOSSFT) == 43); + +enum dos_sft_dev_flags { + DOS_SFT_FLAG_NETWORK = 0x8000, + DOS_SFT_FLAG_TIME_SET = 0x4000, + DOS_SFT_FLAG_CLEAN = 0x0040, + DOS_SFT_DRIVE_MASK = 0x001F +}; + +typedef _Packed struct dos_search_data_block { + uint8_t drive; + char search_templ[11]; + uint8_t search_attr; + uint16_t dir_entry; + uint16_t par_clstr; + char pad1[4]; +} DOSSDB; +STATIC_ASSERT(sizeof(DOSSDB) == 21); + +enum dos_sdb_drive_flags { + DOS_SDB_DRIVE_FLAG_NETWORK = 0x80, + DOS_SDB_DRIVE_MASK = 0x1F +}; + +typedef _Packed struct dos_lock_params { + uint32_t start_offset; + uint32_t region_size; +} DOSLOCK; + +typedef _Packed struct dos_direntry { + char filename[11]; + uint8_t attr; + char pad1[10]; + uint16_t f_time; + uint16_t f_date; + uint16_t start_cluster; + uint32_t f_size; +} DOSDIR; +STATIC_ASSERT(sizeof(DOSDIR) == 32); + +typedef _Packed struct dos_swappable_area { + uint8_t criterr; + uint8_t indos; + uint8_t drive_num; + uint8_t lasterr_dum[9]; + uint8_t __far *cur_dta; + uint16_t cur_psp; + uint8_t pad1[4]; + uint8_t cur_drive; + uint8_t pad2[135]; + char fn1[128]; + char fn2[128]; + DOSSDB sdb; + DOSDIR found_file; + DOSCDS drive_cdscopy; + uint8_t fcb_fn1[11]; + uint8_t pad3[1]; + uint8_t fcb_fn2[11]; + uint8_t pad4[11]; + uint8_t search_attr; + uint8_t open_mode; + uint8_t pad5[51]; + DOSCDS __far *drive_cds; + uint8_t pad6[12]; + uint16_t fn1_csoffset; + uint16_t fn2_csoffset; + uint8_t pad7[71]; + uint16_t openex_act; + uint16_t openex_attr; + uint16_t openex_mode; + uint8_t pad8[29]; + DOSSDB rename_srcfile; + DOSDIR rename_file; +} DOSSDA; +STATIC_ASSERT(offsetof(DOSSDA, cur_dta) == 0xC); +STATIC_ASSERT(offsetof(DOSSDA, cur_drive) == 0x16); +STATIC_ASSERT(offsetof(DOSSDA, fn1) == 0x9E); +STATIC_ASSERT(offsetof(DOSSDA, fn2) == 0x11E); +STATIC_ASSERT(offsetof(DOSSDA, sdb) == 0x19E); +STATIC_ASSERT(offsetof(DOSSDA, found_file) == 0x1B3); +STATIC_ASSERT(offsetof(DOSSDA, search_attr) == 0x24D); +STATIC_ASSERT(offsetof(DOSSDA, open_mode) == 0x24E); +STATIC_ASSERT(offsetof(DOSSDA, drive_cds) == 0x282); +STATIC_ASSERT(offsetof(DOSSDA, openex_act) == 0x2DD); +STATIC_ASSERT(offsetof(DOSSDA, openex_mode) == 0x2E1); + +typedef _Packed struct dos_list_of_lists { + char pad1[22]; + DOSCDS __far *cds; + char pad2[7]; + uint8_t last_drive; +} DOSLOL; + +typedef _Packed struct dos_nls_table { + uint8_t table_id; + void __far *table_data; +} NLSTABLE; + +typedef _Packed struct file_char_table { + uint16_t size; // table size (not counting this word) + uint8_t unk1; // ??? (01h for MS-DOS 3.30-6.00) + uint8_t lowest; // lowest permissible character value for filename + uint8_t highest; // highest permissible character value for filename + uint8_t unk2; // ??? (00h for MS-DOS 3.30-6.00) + uint8_t first_x; // first excluded character in range \ all characters in this + uint8_t last_x; // last excluded character in range / range are illegal + uint8_t unk3; // ??? (02h for MS-DOS 3.30-6.00) + uint8_t n_illegal; // number of illegal (terminator) characters + uint8_t illegal[1]; // characters which terminate a filename: ."/\[]:|<>+=;, +} FCHAR; + +static inline int drive_letter_to_index(char letter) +{ + if (letter >= 'A' && letter <= 'Z') return letter - 'A'; + else if (letter >= 'a' && letter <= 'z') return letter - 'a'; + else return -1; +} + +static inline char drive_index_to_letter(int index) +{ + return 'A' + index; +} + +static inline DOSSDA __far * dos_get_swappable_dos_area(void); +#pragma aux dos_get_swappable_dos_area = \ + "push ds" \ + "mov ax, 0x5D06" \ + "int 0x21" \ + "jnc success" \ + "xor si, si" \ + "mov es, si" \ + "jmp end" \ + "success: mov ax, ds" \ + "mov es, ax" \ + "end: pop ds" \ + __value [es si] \ + __modify [ax cx dx] + +static inline DOSLOL __far * dos_get_list_of_lists(void); +#pragma aux dos_get_list_of_lists = \ + "mov ax, 0x5200" \ + "int 0x21" \ + __value [es bx] \ + __modify [ax] + +static inline uint16_t dos_sft_decref(DOSSFT __far *sft); +#pragma aux dos_sft_decref = \ + "mov ax, 0x1208" \ + "int 0x2F" \ + __parm [es di] \ + __value [ax] + +// Network redirector interface + +enum DOS_REDIR_SUBFUNCTION { + DOS_FN_RMDIR = 0x01, + DOS_FN_MKDIR = 0x03, + DOS_FN_CHDIR = 0x05, + DOS_FN_CLOSE = 0x06, + DOS_FN_COMMIT = 0x07, + DOS_FN_READ = 0x08, + DOS_FN_WRITE = 0x09, + DOS_FN_LOCK = 0x0A, + DOS_FN_UNLOCK = 0x0B, + DOS_FN_GET_DISK_FREE = 0x0C, + DOS_FN_SET_FILE_ATTR = 0x0E, + DOS_FN_GET_FILE_ATTR = 0x0F, + DOS_FN_RENAME = 0x11, + DOS_FN_DELETE = 0x13, + DOS_FN_OPEN = 0x16, + DOS_FN_CREATE = 0x17, + DOS_FN_FIND_FIRST = 0x1B, + DOS_FN_FIND_NEXT = 0x1C, + DOS_FN_CLOSE_ALL = 0x1D, + DOS_FN_DO_REDIR = 0x1E, + DOS_FN_PRINTSETUP = 0x1F, + DOS_FN_FLUSH = 0x20, + DOS_FN_SEEK_END = 0x21, + DOS_FN_ATEXIT = 0x22, + DOS_FN_QUALIFY = 0x23, + DOS_FN_OPEN_EX = 0x2E +}; + +enum OPENEX_ACTIONS { + OPENEX_FAIL_IF_EXISTS = 0x00, + OPENEX_OPEN_IF_EXISTS = 0x01, + OPENEX_REPLACE_IF_EXISTS = 0x02, + OPENEX_FAIL_IF_NEW = 0x00, + OPENEX_CREATE_IF_NEW = 0x10, +}; + +enum OPENEX_MODE { + OPENEX_MODE_READ = 0, + OPENEX_MODE_WRITE = 1, + OPENEX_MODE_RDWR = 2, + OPENEX_MODE_MASK = 3, + OPENEX_SHARE_COMPAT = 0x00, + OPENEX_SHARE_DENYALL = 0x10, + OPENEX_SHARE_DENYWRITE = 0x20, + OPENEX_SHARE_DENYREAD = 0x30, + OPENEX_SHARE_DENYNONE = 0x40, + OPENEX_SHARE_MASK = 0x70, +}; + +enum OPENEX_RESULT { + OPENEX_FILE_OPENED = 1, + OPENEX_FILE_CREATED = 2, + OPENEX_FILE_REPLACED = 3, +}; + +#endif // DOSINT_H diff --git a/dos/inc/mbr.h b/dos/inc/mbr.h new file mode 100644 index 0000000..d45727d --- /dev/null +++ b/dos/inc/mbr.h @@ -0,0 +1,46 @@ +#ifndef MBR_H +#define MBR_H + +#include "sassert.h" + +enum MBR_PARTTYPE +{ + MBR_EMPTY = 0, + MBR_FAT12 = 0x01, + MBR_FAT16_32MB = 0x04, + MBR_EXT = 0x05, + MBR_FAT16 = 0x06, + MBR_IFS = 0x07, + MBR_FAT32 = 0x0B, + MBR_FAT32_LBA = 0x0C, + MBR_FAT16_LBA = 0x0E, + MBR_EXT_LBA = 0x0F, + MBR_LINUX_SWAP = 0x82, + MBR_LINUX = 0x83, +}; + +typedef _Packed struct MBR_CHS +{ + uint8_t head; + unsigned sector : 6; + unsigned cyl : 10; +} MBR_CHS; + +STATIC_ASSERT(sizeof(MBR_CHS) == 3); + +typedef _Packed struct MBR_PARTENTRY +{ + uint8_t status; + MBR_CHS chsFirst; + uint8_t type; + MBR_CHS chsLast; + uint32_t lbaFirst; + uint32_t lbaLen; +} MBR_PARTENTRY; + +STATIC_ASSERT(sizeof(MBR_PARTENTRY) == 16); + +#define MBR_PARTENTRY_OFFSET_ON_DISK 446 +#define MBR_SIGNATURE_OFFSET_ON_DISK 510 + +#endif // MBR_H diff --git a/dos/makefile b/dos/makefile new file mode 100644 index 0000000..aa328c2 --- /dev/null +++ b/dos/makefile @@ -0,0 +1,66 @@ +#!/usr/bin/env wmake + +# Debug Settings +!ifeq debug 1 +# Dump debug log to serial port, 0x3F8 UART (COM1) +debug_cflags = -DENABLE_DLOG=1 -DDLOG_TARGET=DLOG_TARGET_SERIAL -DDLOG_TARGET_PORT=0x3F8 +!endif + +# All binaries to build +bins = chudisk.sys chud.exe utest.exe + +# Object files for chudisk +chudisk_objs = chudisk.obj chudinit.obj cio.obj + +# Compiler arguments for DOS +dos_cflags = -bt=dos -i=../inc -3 -ms -osi -ox -w3 -wcd=202 $(debug_cflags) +# -ms to use small memory model (this assumes ss == ds) +# -osi to optimize for size, put intrinsics inline (to avoid runtime calls) +# -ox extra optimization (impact is not very big) +# -w3 enables warnings +# -wcd=202 disables the unreferenced function warning (e.g., for inline functions in headers) + +# Compiler arguments for the DOS drivers +dosdrv_cflags = $(dos_cflags) -zu -s -g=DGROUP +# -zu since ss != ds in the driver +# -s to disable stack checks, since it inserts calls to the runtime +# -g=DGROUP put everything into the same segment + +# Full compiler command lines +compile_dos = *wcc -fo=$^@ $(dos_cflags) $[@ +compile_dosdrv = *wcc -fo=$^@ $(dosdrv_cflags) $[@ + +# Object files + +.BEFORE: + # We need DOS and Windows headers, not host platform's + set include=$(%watcom)/h/win;$(%watcom)/h + +all: $(bins) .SYMBOLIC + +chudisk.sys: chudisk.lnk $(chudisk_objs) .AUTODEPEND + *wlink @$[@ name $@ file { $(chudisk_objs) } + +chudisk.obj: chudisk.c .AUTODEPEND + $(compile_dosdrv) + +chudinit.obj: chudinit.c .AUTODEPEND + $(compile_dosdrv) + +cio.obj: cio.c .AUTODEPEND + $(compile_dosdrv) + +chud.exe: chud.obj .AUTODEPEND + *wlink name $@ system dos file { chud.obj } + +chud.obj: chud.c .AUTODEPEND + $(compile_dos) + +utest.exe: utest.obj .AUTODEPEND + *wlink system dos name $@ file { utest.obj } + +utest.obj: utest.c .AUTODEPEND + $(compile_dos) + +clean: .SYMBOLIC + rm -f $(bins) *.obj diff --git a/dos/utest.c b/dos/utest.c new file mode 100644 index 0000000..7aa8750 --- /dev/null +++ b/dos/utest.c @@ -0,0 +1,500 @@ +#include +#include +#include +#include + +#include "ch375dos.h" + +struct ch375 ch375_dev = { + .io_base = 0x260 +}; + +#define SWAPNC_32(w) (\ + (((w) & 0x000000FFUL) << 24) |\ + (((w) & 0x0000FF00UL) << 8) |\ + (((w) & 0x00FF0000UL) >> 8) |\ + (((w) & 0xFF000000UL) >> 24)\ + ) + +static uint8_t buffer[64]; + +typedef uint16_t Centisecs; +#define CENTISECS_MAX (60 * 100U) + +/// Gets the current wallclock centisecond (1/100 of a second). +// Note this wraps around every minute, so max value is CENTISECS_MAX. +static Centisecs get_centisecs() +{ + struct dostime_t now; + _dos_gettime(&now); + return (now.second * 100U) + now.hsecond; +} + +static inline Centisecs centisecs_remaining(Centisecs start, Centisecs deadline) +{ + Centisecs now = get_centisecs(); + if (now < start) { + // Rollover happened + now += CENTISECS_MAX; + } + if (now >= deadline) { + return 0; + } else { + return deadline - now; + } +} + +static void delay_centisecs(Centisecs timeout) +{ + Centisecs start = get_centisecs(); + Centisecs deadline = start + timeout; + while (centisecs_remaining(start, deadline)) { + printf("Delay deadline=%u now=%u\n", deadline, get_centisecs()); + } +} + + +typedef bool (*PollFunction)(void); + +static bool wait_with_timeout(Centisecs timeout, PollFunction pf) { + Centisecs start = get_centisecs(); + Centisecs deadline = start + timeout; + Centisecs last_print = 0; + while (!pf() && centisecs_remaining(start, deadline)) { + if (last_print != get_centisecs()) { + printf("Wait with timeout deadline=%u now=%u\n", deadline, get_centisecs()); + last_print = get_centisecs(); + } + } + if (pf()) { + printf("Wait OK, %d centisecs elapsed\n", get_centisecs() - start); + return true; + } else { + printf("Wait timeout\n"); + return false; + } + //return pf(); +} + +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, poll_interrupt); +} + +static void dump_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_dev); + delay_centisecs(5); +} + +void chip_sleep() +{ + printf("Chip sleep\n"); + ch375_enter_sleep(&ch375_dev); +} + +void usb_host_off() +{ + printf("Set usb mode disabled...\n"); + 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"); + } +} + +void usb_host_reset(void) +{ + printf("Set usb mode reset...\n"); + 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"); + } +} + +bool usb_host_on(void) +{ + printf("Set usb mode...\n"); + 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; + } + + dump_int_status(); + + return true; +} + +bool usb_host_connect(void) +{ + uint8_t status; + + printf("Test connect...\n"); + ch375_test_connect(&ch375_dev); + if (!wait_with_timeout(1, poll_test_connect)) { + printf("Test connect timedout\n"); + } + status = ch375_read_data(&ch375_dev); + printf("Test connect status = 0x%hx\n", status); + switch (status) { + case CH375_INT_USB_CONNECT: + printf("USB device connected\n"); + return true; + case CH375_INT_USB_READY: + printf("USB device ready\n"); + return true; + case CH375_INT_USB_DISCONNECT: + printf("USB device not connected\n"); + default: + return false; + } +} + +static int usb_host_wait_int_read_data(Centisecs timeout, uint8_t buffer[]) +{ + uint8_t status; + if (!wait_with_timeout(timeout, poll_interrupt)) { + return -1; + } + + // Get and acknowledge interrupt + 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_dev, CH375_RD_USB_DATA0); + return ch375_read_data_block(&ch375_dev, buffer); +} + +static bool init_disk() +{ + uint8_t status; + + printf("Disk init...\n"); + ch375_disk_init(&ch375_dev); + if (!wait_interrupt_with_timeout(100)) { + printf("Disk init timeout\n"); + } + status = ch375_get_int_status(&ch375_dev); + printf("Disk init status = 0x%hx\n", status); + + return status == CH375_INT_USB_SUCCESS; +} + +static bool disk_ready() +{ + uint8_t status; + ch375_test_disk_ready(&ch375_dev); + if (!wait_interrupt_with_timeout(100)) { + printf("Disk ready timeout\n"); + } + status = ch375_get_int_status(&ch375_dev); + printf("Disk ready status = 0x%hx\n", status); + return status == CH375_INT_USB_SUCCESS; +} + +static void disk_size() +{ + uint8_t status; + + printf("Disk size...\n"); + ch375_send_command(&ch375_dev, CH375_DISK_SIZE); + if (!wait_interrupt_with_timeout(100)) { + printf("Disk size timeout\n"); + } + 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_dev, CH375_RD_USB_DATA); + + len = ch375_read_data_block(&ch375_dev, buffer); + printf("Read %d bytes\n", len); + + if (len >= 8) { + uint32_t sectors = SWAPNC_32(*(uint32_t*)&buffer[0]), + bytes_per_sector = SWAPNC_32(*(uint32_t*)&buffer[4]); + uint32_t kbytes = sectors / 2; + uint32_t mbytes = kbytes / 1024; + + printf("%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]); + printf("%lu sectors, %lu bytes per sector, %u MiB\n", sectors, bytes_per_sector, mbytes); + } + } +} + +int disk() +{ + bool ready = disk_ready(); + int i = 0; + + if (ready) { + printf("Disk ready\n"); + } else { + printf("Disk not ready\n"); + + if (!usb_host_connect()) { + return EXIT_FAILURE; + } + + if (!init_disk()) { + return EXIT_FAILURE; + } + + i = 3; + while (i >= 0 && !(ready = disk_ready())) { + i--; + printf("Give more time..\n"); + } + + if (ready) { + printf("Disk now ready\n"); + } else { + printf("Disk NOT ready\n"); + return EXIT_FAILURE; + } + } + + dump_int_status(); + + disk_size(); + + return EXIT_SUCCESS; +} + +int disk_read(uint32_t lba) +{ + uint8_t status; + unsigned total = 0; + unsigned i; + 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_dev) == CH375_INT_USB_DISK_READ)) { + int len; + ch375_send_command(&ch375_dev, CH375_RD_USB_DATA); + + if (total >= 512) { + printf("Read buffer overflow!"); + break; + } + + 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_dev, CH375_DISK_RD_GO); + if (!wait_interrupt_with_timeout(100)) { + printf("Disk read more timeout\n"); + } + } else if (len == 0) { + break; + } + } + printf("Disk read status = 0x%hx\n", status); + + printf("Disk read total bytes = %u\n", total); + + for (i = 512 - (8*4); i < total; i+=8) { + printf("%hx %hx %hx %hx %hx %hx %hx %hx\n", + buffer[i+0], buffer[i+1], buffer[i+2], buffer[i+3], + buffer[i+4], buffer[i+5], buffer[i+6], buffer[i+7]); + } + + return EXIT_SUCCESS; +} + +int autou() +{ + uint8_t status; + +#if 1 + usb_host_reset(); + delay(100); +#endif + +#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_dev, CH375_USB_SPEED_LOW); +#endif + +#if 1 + printf("Auto setup...\n"); + + 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(&ch375_dev); + printf("Auto setup status = 0x%hx\n", status); + + delay(100); +#endif + + if (status == CH375_INT_USB_SUCCESS) { + printf("Auto setup success\n"); + } else { + printf("Auto setup fail, status=0x%x\n", status); + } + + dump_int_status(); + + return EXIT_SUCCESS; +} + +int host() +{ + usb_device_descriptor *dev_descr; + int n; + + //ch375_set_retry(&ch375_dev, CH375_RETRY_NAK_INFINITE | CH375_RETRY_TIMEOUT_MAX); + +#if 0 + usb_host_reset(); + //delay(100); +#endif + +#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_dev, CH375_USB_SPEED_LOW); +#endif + +#if 0 + if (!usb_host_connect()) { + return EXIT_FAILURE; + } +#endif + + dump_int_status(); + + printf("Reading descriptor %d...\n", 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); + } else { + printf("Failed to get descriptor 1, err=0x%hx\n", -n); + return EXIT_FAILURE; + } + + dump_int_status(); + + dev_descr = (usb_device_descriptor*)buffer; + + 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(&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); + } else { + printf("Failed to get descriptor 2, err=0x%hx\n", -n); + return EXIT_FAILURE; + } + + dump_int_status(); + + return EXIT_SUCCESS; +} + +int main(int argc, const char *argv[]) +{ + 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 using IO port 0x%x\n", mode, ch375_dev.io_base); + + dump_int_status(); + + switch (mode) { + case 'd': + return disk(); + case 'e': + return disk_read(0); + case 'a': + return autou(); + case 'm': + return host(); + case 'h': + usb_host_on(); + break; + case 'c': + usb_host_connect(); + break; + case 'o': + usb_host_off(); + break; + case 'b': + usb_host_reset(); + break; + case 's': + chip_sleep(); + break; + case 'r': + chip_reset(); + break; + case 'i': + dump_int_status(); + break; + case 'v': + printf("IC Version: 0x%hx\n", ch375_get_version(&ch375_dev)); + break; + case '?': + printf("test modes: d e a m h c o b s r i v\n"); + break; + default: + printf("Unknown mode %c\n", mode); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/dos/utils.h b/dos/utils.h new file mode 100644 index 0000000..138a774 --- /dev/null +++ b/dos/utils.h @@ -0,0 +1,60 @@ +/* + * CHU - driver for CH375 in disk mode + * Copyright (C) 2024 Javier S. Pedro + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#ifndef UTILS_H +#define UTILS_H + +#include + +static inline __segment get_cs(void); +#pragma aux get_cs = "mov ax, cs" value [ax] modify exact [] nomemory + +static inline __segment get_ds(void); +#pragma aux get_ds = "mov ax, ds" value [ax] modify exact [] nomemory + +static void load_ds_from_cs(void); +#pragma aux load_ds_from_cs = \ + "push cs" \ + "pop ds" \ + __modify __exact [] + +#define SWAPNC_32(w) (\ + (((w) & 0x000000FFUL) << 24) |\ + (((w) & 0x0000FF00UL) << 8) |\ + (((w) & 0x00FF0000UL) >> 8) |\ + (((w) & 0xFF000000UL) >> 24)\ + ) + +static inline uint16_t div(uint32_t dividend, uint16_t divisor); +#pragma aux div = \ + "div bx" \ + __parm [dx ax] [bx] \ + __value [ax] \ + __modify __exact [dx ax] + +static inline uint32_t mul(uint16_t a, uint16_t b); +#pragma aux mul = \ + "mul dx" \ + __parm [ax] [dx] \ + __value [dx ax] \ + __modify __exact [dx ax] + + + +#endif // UTILS_H diff --git a/inc/bda.h b/inc/bda.h deleted file mode 100644 index b447221..0000000 --- a/inc/bda.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * VBMouse - BIOS data area access routines - * Copyright (C) 2022 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef BDA_H -#define BDA_H - -#define BIOS_DATA_AREA_SEGMENT 0x40 - -static inline uint32_t bda_get_dword(unsigned int offset) { - uint32_t __far *p = MK_FP(BIOS_DATA_AREA_SEGMENT, offset); - return *p; -} - -static inline uint16_t bda_get_word(unsigned int offset) { - uint16_t __far *p = MK_FP(BIOS_DATA_AREA_SEGMENT, offset); - return *p; -} - -static inline uint8_t bda_get_byte(unsigned int offset) { - uint8_t __far *p = MK_FP(BIOS_DATA_AREA_SEGMENT, offset); - return *p; -} - -#define bda_get_ebda_segment() bda_get_word(0x0e) - -#define bda_get_equipment() bda_get_word(0x10) - -#define bda_get_video_mode() bda_get_byte(0x49) -#define bda_get_num_columns() bda_get_word(0x4a) -#define bda_get_video_page_size() bda_get_word(0x4c) -#define bda_get_cur_video_page() bda_get_word(0x62) -#define bda_get_last_row() bda_get_byte(0x84) -#define bda_get_char_height() bda_get_word(0x85) - -#define bda_get_tick_count() bda_get_dword(0x6c) -#define bda_get_tick_count_lo() bda_get_word(0x6c) - -static inline void bda_wait_tick() { - uint16_t cur_ticks = bda_get_tick_count_lo(); - do { - //pause(); - } while (cur_ticks == bda_get_tick_count_lo()); -} - -static inline void bda_wait_ticks(unsigned int ticks) { - for (; ticks > 0; ticks--) { - bda_wait_tick(); - } -} - -#endif // BDA_H diff --git a/inc/bpb.h b/inc/bpb.h deleted file mode 100644 index 8df04a1..0000000 --- a/inc/bpb.h +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef BPB_H -#define BPB_H - -#include -#include "sassert.h" - -#define DOS_BPB_OFFSET_ON_DISK 0x0B - -typedef _Packed struct DOS_BPB_DOS3 -{ - /** Number of bytes per sector. */ - uint16_t bytesPerSector; - /** Number of sectors per cluster. Must be power of 2. */ - uint8_t sectorsPerCluster; - /** Number of reserved sectors. */ - uint16_t reservedSectors; - /** Number of FATs. */ - uint8_t numberOfFATs; - /** Maximum number of entries in the root directory. */ - uint16_t rootDirEntries; - /** Total number of sectors on the drive. If drive larger than 32MB, this field is zero, see numberOfSectorsLarge. */ - uint16_t numberOfSectors; - /** Media descriptor byte. */ - uint8_t mediaType; - /** Number of sectors used by a FAT. */ - uint16_t sectorsPerFAT; - /** Number of sectors per track. */ - uint16_t sectorsPerTrack; - /** Number of heads. */ - uint16_t numberOfHeads; - /** Number of hidden sectors on the drive, from the start of the drive. */ - uint32_t hiddenSectors; - /** Number of sectors, only used if drive > 32MB. */ - uint32_t numberOfSectorsLarge; -} DOS_BPB_DOS3; -STATIC_ASSERT(sizeof(DOS_BPB_DOS3) == 25); - -typedef _Packed struct DOS_BPB_DOS4 -{ - struct DOS_BPB_DOS3; - uint8_t physicalDriveNumber; - uint8_t flags; - uint8_t extendedBootSignature; - uint32_t volumeSerialNumber; - char volumeLabel[11]; - char fsType[8]; -} DOS_BPB_DOS4; -STATIC_ASSERT(sizeof(DOS_BPB_DOS4) == 25 + 26); - -typedef _Packed struct DOS_BPB_FAT32 -{ - struct DOS_BPB_DOS3; - uint32_t sectorPerFatLarge; - uint16_t extendedFlags; - uint16_t version; - uint32_t rootDirCluster; - uint16_t infoSector; - uint16_t backupBootSector; - - char bootFileName[12]; - uint8_t physicalDriveNumber; - uint8_t flags; - uint8_t extendedBootSignature; - uint32_t volumeSerialNumber; - char volumeLabel[11]; - char fsType[8]; -} DOS_BPB_FAT32; -STATIC_ASSERT(sizeof(DOS_BPB_FAT32) == 25 + 54); - -/** Union of all BPB types. Default is DOS4 / FAT16. */ -typedef union DOS_BPB -{ - struct DOS_BPB_DOS3; - struct DOS_BPB_DOS4 fat16; - struct DOS_BPB_FAT32 fat32; -} DOS_BPB; - -typedef enum DOS_BPB_MEDIA_TYPE -{ - DOS_BPB_MEDIA_HDD = 0xF8 -} DOS_BPB_MEDIA_TYPE; - -#endif // BPB_H diff --git a/inc/ch375.h b/inc/ch375.h new file mode 100644 index 0000000..5ce90a2 --- /dev/null +++ b/inc/ch375.h @@ -0,0 +1,361 @@ +/* + * CH375 hardware header + * Copyright (C) 2024 Javier S. Pedro + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#ifndef CH375_H +#define CH375_H + +#include "usb.h" + +/** Standard defines */ + +struct ch375; + +enum { + CH375_PORT_DATA = 0, + CH375_PORT_CMD = 1, +}; + +enum ch375_command { + /** Returns chip and firmware version in bit 5-0. + * output: 1byte. + * C375B is 0x37. */ + CH375_GET_IC_VERSION = 0x01, + /** (Host mode) Set serial port baud rate. */ + CH375_SET_BAUDRATE = 0x02, + /** Enter low power mode. Wakes up with USB or host activity. */ + CH375_ENTER_SLEEP = 0x03, + /** Manualy force USB speed. + * Input: 1 byte: + * 0: Full speed + * 1: Full speed with low speed clock frequency (??) + * 2: Low speed */ + CH375_SET_USB_SPEED = 0x04, + /** Hardware reset. May take up to 40ms. */ + CH375_RESET = 0x05, + /** Simple eco function. Returns input byte negated. + * Input: 1 byte: x + * Output: 1 byte: ~x */ + CH375_CHECK_EXIST = 0x06, + + CH375_READ_REG = 0x0A, + CH375_WRITE_REG = 0x0B, + /** Sets a 100us timer. + * Output: infinite bytes. 0 while timer not expired, non-0 when expired. */ + CH375_DELAY_100US = 0x0F, + /** (Device mode) set our USB vid and PID. + * Input: 4 bytes: VID LSB, VID MSB, PID LSB, PID MSB */ + CH375_SET_USB_ID = 0x12, + /** (Device mode) set USB address. ?? */ + CH375_SET_USB_ADDR = 0x13, + /** Set USB mode. Usually takes 20µs. + * Input: 1 byte: + * 0 : Device mode disabled (i.e. device plug-in detection) + * 1 : Device mode with external firmware + * 2 : Device mode with built-in firmware + * 4 : Host mode disabled (i.e. device plug-in detection) + * 5 : Host mode without automatic SOF packets. + * 6 : Host mode with automatic SOF packets. + * 7 : Host mode, initiate bus reset + * Output: 1 byte: + * CMD_RET_SUCCESS if OK + * CMD_RET_ABORT, etc. if error + */ + CH375_SET_USB_MODE = 0x15, + /** (Host mode) Check the connection status. + * Output: 1 byte: either USB_INT_CONNECT or USB_INT_DISCONNECT or USB_INT_USB_READY. */ + CH375_TEST_CONNECT = 0x16, + /** Abandon retrying and return NAK. */ + CH375_ABORT_NAK = 0x17, + + CH375_SET_ENDP2 = 0x18, + CH375_SET_ENDP3 = 0x19, + CH375_SET_ENDP4 = 0x1A, + CH375_SET_ENDP5 = 0x1B, + CH375_SET_ENDP6 = 0x1C, + CH375_SET_ENDP7 = 0x1D, + + /** Get interrupt status and ack pending interrupts. + * Output: 1 byte */ + CH375_GET_INT_STATUS = 0x22, + /** (Device mode) Release current USB buffer. */ + CH375_UNLOCK_USB = 0x23, + /** Read buffer of endpoint of current interrupt. + * Output: length byte + data. */ + CH375_RD_USB_DATA0 = 0x27, + /** Equivalent to RD_USB_DATA0 + UNLOCK_USB. */ + CH375_RD_USB_DATA = 0x28, + CH375_WR_USB_DATA3 = 0x29, + CH375_WR_USB_DATA5 = 0x2A, + CH375_WR_USB_DATA7 = 0x2B, + /** (Host mode) Clear error state on endpoint. + * Input: 1 byte: endpoint number. + * On done: Triggers interrupt. */ + CH375_CLR_STALL = 0x41, + /** (Host mode) Set USB address. + * Input: USB address. + * On done: Triggers interrupt. */ + CH375_SET_ADDRESS = 0x45, + CH375_GET_DESCR = 0x46, + CH375_SET_CONFIG = 0x49, + CH375_AUTO_SETUP = 0x4D, + CH375_ISSUE_TKN_X = 0x4E, + CH375_ISSUE_TOKEN = 0x4F, + + CH375_DISK_BOC = 0x50, + CH375_DISK_INIT = 0x51, + CH375_DISK_RESET = 0x52, + CH375_DISK_SIZE = 0x53, + CH375_DISK_READ = 0x54, + CH375_DISK_RD_GO = 0x55, + CH375_DISK_WRITE = 0x56, + CH375_DISK_WR_GO = 0x57, + CH375_DISK_INQUIRY = 0x58, + CH375_DISK_READY = 0x59, + CH375_DISK_R_SENSE = 0x5A, +}; + +enum ch375_cmd_ret { + CH375_RET_SUCESS = 0x51, + CH375_RET_ABORT = 0x5F +}; + +enum ch375_int { + /** if (x & mask) == val, this is a USB device mode interrupt. */ + CH375_INT_DEVICE_MASK = 0xF0, + CH375_INT_DEVICE_VAL = 0x00, + + CH375_INT_HOST_MASK = 0xE0, + CH375_INT_HOST_VAL = 0x20, + + CH375_INT_BUS_RESET1 = 0x03, + CH375_INT_BUS_RESET2 = 0x07, + CH375_INT_BUS_RESET3 = 0x0B, + CH375_INT_BUS_RESET4 = 0x0F, + + /** USB bus suspend event. */ + CH375_INT_USB_SUSPEND = 0x05, + /** Wakeup from USB suspend. */ + CH375_INT_USB_WAKE_UP = 0x06, + CH375_INT_USB_SUCCESS = 0x14, + CH375_INT_USB_CONNECT = 0x15, + CH375_INT_USB_DISCONNECT = 0x16, + CH375_INT_USB_BUF_OVER = 0x17, + CH375_INT_USB_READY = 0x18, + CH375_INT_USB_DISK_READ = 0x1D, + CH375_INT_USB_DISK_WRITE = 0x1E, + CH375_INT_USB_DISK_ERR = 0x1F, +}; + +enum CH375_REG { + /** (Host mode) Get data rate of current USB device. + * Bit 4: if 1= Low Speed. if 0 = Full Speed.*/ + CH375_REG_DEV_RATE = 0x0A, + /** (Device mode): how to check USB bus suspend status. + * 0: Do not check + * 4: Check at 50ms intervals + * 5: Check at 10ms intervals */ + CH375_REG_CHK_SUSPEND = 0x10, + CH375_REG_TOGGLE = 0x1A, + + /** ??? + * Bit 5 : set after disk is online? but never unset... */ + CH375_REG_DISK_STATUS = 0x20, + /** (Host mode) Set the number of retries for USB transactions. + * If bit 7 is 0, no retry is performed when NAK is received. + * If bit 7 is 1 and bit 6 is 0, unlimited retries are performed when NAK is received. + * If bit 7 is 1 and bit 6 is 1, the maximum retry is 2 seconds when NAK is received. + * Bits 5-0 are the number of retries after timeout. */ + CH375_REG_RETRY = 0x25, + /** (Host disk mode) Set the current LUN. */ + CH375_REG_DISK_LUN = 0x34, + CH375_REG_DISK_MAX_LUN = 0x38, + /** (Host disk mode) Set the total number of 64-byte packets per sector. + * E.g. 8 = 512 byte sectors. */ + CH375_REG_DISK_PKT_P_SEC = 0x39, + /// Drive letter (in ASCII) currently assigned. This is set by software. + CH375_REG_DISK_NAME = 0x3E + + // 0x1B what's on this register ? 0x7F , 0x80 values seen + // 0x20 ?? some type of status ? + // 0x31, 0x32, 0x33 ?? + +}; + +#define CH375_BLOCK_SIZE 64 + +// HELPER ROUTINES + +/* You have to define these routines for the helper functions to work. */ + +static inline uint8_t ch375_read_data(struct ch375 *dev); +static inline void ch375_write_data(struct ch375 *dev, uint8_t data); + +static inline void ch375_send_command(struct ch375 *dev, enum ch375_command cmd); +static inline bool ch375_poll_interrupt(struct ch375 *dev); + +// GLOBAL COMMANDS + +static inline uint8_t ch375_get_version(struct ch375 *dev) +{ + ch375_send_command(dev, CH375_GET_IC_VERSION); + return ch375_read_data(dev) & 0x3F; +} + +static inline void ch375_enter_sleep(struct ch375 *dev) +{ + ch375_send_command(dev, CH375_ENTER_SLEEP); +} + +static inline void ch375_reset(struct ch375 *dev) +{ + ch375_send_command(dev, CH375_RESET); +} + +static inline uint8_t ch375_check_exists(struct ch375 *dev, uint8_t val) +{ + ch375_send_command(dev, CH375_CHECK_EXIST); + ch375_write_data(dev, val); + return ch375_read_data(dev); +} + +static inline uint8_t ch375_get_int_status(struct ch375 *dev) +{ + ch375_send_command(dev, CH375_GET_INT_STATUS); + return ch375_read_data(dev); +} + +static uint8_t ch375_read_reg(struct ch375 *dev, enum CH375_REG reg) +{ + ch375_send_command(dev, CH375_READ_REG); + ch375_write_data(dev, reg); + return ch375_read_data(dev); +} + +static void ch375_write_reg(struct ch375 *dev, enum CH375_REG reg, uint8_t value) +{ + ch375_send_command(dev, CH375_WRITE_REG); + ch375_write_data(dev, reg); + ch375_write_data(dev, value); +} + +enum ch375_usb_mode { + CH375_USB_MODE_DEVICE_DISABLED = 0, + CH375_USB_MODE_DEVICE_EXTERNAL_FIRMWARE = 1, + CH375_USB_MODE_DEVICE_INTERNAL_FIRMWARE = 2, + CH375_USB_MODE_HOST_DISABLED = 4, + CH375_USB_MODE_HOST = 5, + CH375_USB_MODE_HOST_AUTO_SOF = 6, + CH375_USB_MODE_HOST_RESET = 7 +}; + +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 { + CH375_USB_SPEED_FULL = 0, + CH375_USB_SPEED_LOW = 2 +}; + +// Seems to be for CH375B only. See below for CH375A alternative. +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(struct ch375 *dev) { + // Undocumented. + ch375_write_reg(dev, 0x17, 0xd8); +} + +// HOST COMMANDS + +static void ch375_test_connect(struct ch375 *dev) { + ch375_send_command(dev, CH375_TEST_CONNECT); +} + +static void ch375_auto_setup(struct ch375 *dev) { + ch375_send_command(dev, CH375_AUTO_SETUP); +} + +enum ch375_retry_mode { + CH375_RETRY_NAK_INFINITE = 0x80, + CH375_RETRY_NAK_2S = 0xC0, + CH375_RETRY_NAK_NONE = 0x00, + + CH375_RETRY_TIMEOUT_MAX = 0x1F +}; + +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(struct ch375 *dev, usb_descriptor_type type) { + ch375_send_command(dev, CH375_GET_DESCR); + ch375_write_data(dev, type); +} + +// DISK COMMANDS + +enum ch375_reg_disk_status { + CH375_REG_DISK_STATUS_READY = 0x20 +}; + +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) +{ + ch375_send_command(dev, CH375_DISK_INIT); +} + +static void ch375_disk_read(struct ch375 *dev, uint32_t lba, uint8_t nsectors) +{ + 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_write(struct ch375 *dev, uint32_t lba, uint8_t 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_test_disk_ready(struct ch375 *dev) +{ + ch375_send_command(dev, CH375_DISK_READY); +} + +static void ch375_disk_r_sense(struct ch375 *dev) +{ + ch375_send_command(dev, CH375_DISK_R_SENSE); +} + +#endif diff --git a/inc/chuver.h b/inc/chuver.h new file mode 100644 index 0000000..f816114 --- /dev/null +++ b/inc/chuver.h @@ -0,0 +1,7 @@ +#ifndef CHUVER_H +#define CHUVER_H + +#define CHUDISK_VERSION_MAJOR 1 +#define CHUDISK_VERSION_MINOR 0x0 + +#endif /* CHUVER_H */ diff --git a/inc/dlog.h b/inc/dlog.h new file mode 100644 index 0000000..ab6f4e9 --- /dev/null +++ b/inc/dlog.h @@ -0,0 +1,439 @@ +/* + * VBMouse - printf & logging routines + * Copyright (C) 2022 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef DLOG_H +#define DLOG_H + +#include +#include +#include +#include +#include + +// Customizable defines +/** If 0, these routines become nops */ +#ifndef ENABLE_DLOG +#define ENABLE_DLOG 0 +#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). */ +#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 + +/** 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 DLOG_TARGET == DLOG_TARGET_BIOSTTY + +static inline void dlog_init() +{ + // No initialization required +} + +static inline void dconputc(char c); +#pragma aux dconputc = \ + "mov ah, 0x0E" \ + "xor bx, bx" \ + "int 0x10" \ + __parm [AL] \ + __modify __exact [AH AL BH BL]; + +static inline void dputc(char c) +{ + // Need to convert CR into CRLF... + if (c == '\n') { + dconputc('\n'); + dconputc('\r'); + } else { + dconputc(c); + } +} + + +#elif DLOG_TARGET == DLOG_TARGET_SERIAL + +static void dlog_init() +{ + // Comes from https://wiki.osdev.org/Serial_Ports#Initialization + outp(DLOG_TARGET_PORT + 1, 0x00); // Disable all interrupts + outp(DLOG_TARGET_PORT + 3, 0x80); // Enable DLAB (set baud rate divisor) + outp(DLOG_TARGET_PORT + 0, 0x01); // Set divisor to 1 (lo byte) 115200 baud + outp(DLOG_TARGET_PORT + 1, 0x00); // (hi byte) + outp(DLOG_TARGET_PORT + 3, 0x03); // 8 bits, no parity, one stop bit + outp(DLOG_TARGET_PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold + outp(DLOG_TARGET_PORT + 4, 0x03); // RTS/DSR set, IRQs disabled +} + +static inline void dputc(char c) +{ + while (!(inp(DLOG_TARGET_PORT + 5) & 0x20)); + outp(DLOG_TARGET_PORT, c); +} + +#elif DLOG_TARGET == DLOG_TARGET_IOPORT + +static inline void dlog_init() +{ + // No initialization required +} + +static inline void dputc(char c) +{ + outp(DLOG_TARGET_PORT, c); +} + +#endif /* DLOG_TARGET */ + +static void d_utoa(unsigned num, unsigned base) +{ +#if UINT_MAX > 99999 + char buf[12]; +#else + char buf[6]; +#endif + int i = 0; + + do { + unsigned digit = num % base; + + if (digit < 10) { + buf[i] = '0' + digit; + } else { + buf[i] = 'a' + (digit - 10); + } + + i++; + num /= base; + } while (num > 0 && i < sizeof(buf)); + + while (i--) { + dputc(buf[i]); + } +} + +static void d_ultoa(unsigned long num, unsigned base) +{ + char buf[12]; + int i = 0; + + do { + unsigned digit = 0; + + // Do unsigned long division with remained on whichever platform we are + static inline void uldiv(void); +#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]" \ + "movzx ecx, [base]" \ + "xor edx, edx" \ + \ + "div ecx" \ + \ + "mov [num], eax" \ + "mov [digit], dx" \ + \ + "pop edx" \ + "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) { + buf[i] = '0' + digit; + } else { + buf[i] = 'a' + (digit - 10); + } + + i++; + } while (num > 0 && i < sizeof(buf)); + + while (i--) { + dputc(buf[i]); + } +} + +static void d_itoa(int num, unsigned base) +{ + unsigned unum; + + if (num < 0) { + dputc('-'); + unum = -num; + } else { + unum = num; + } + + d_utoa(unum, base); +} + +static void d_ltoa(long num, unsigned base) +{ + unsigned long unum; + + if (num < 0) { + dputc('-'); + unum = -num; + } else { + unum = num; + } + + d_ultoa(unum, base); +} + +static unsigned d_strtou(const char * __far *str) +{ + unsigned i = 0; + const char *s = *str; + char c = *s; + while (c >= '0' && c <= '9') { + i = (i * 10) + (c - '0'); + c = *(++s); + } + *str = s; + return i; +} + +static void d_printstr(const char __far *s, unsigned l) +{ + while (l > 0 && *s) { + dputc(*s++); + l--; + } +} + +static void dprintf(const char *fmt, ...) +{ + va_list va; + char c; + + va_start(va, fmt); + + while ((c = *(fmt++))) { + if (c == '%') { + unsigned width, precision, size; + bool is_far = false; + + width = d_strtou(&fmt); + + if ((c = *fmt) == '.') { + fmt++; + precision = d_strtou(&fmt); + } else { + precision = UINT_MAX; + } + + switch ((c = *fmt)) { + case 'l': + size = sizeof(long); + fmt++; + break; + case 'h': + size = sizeof(short); + fmt++; + break; + case 'F': + is_far = true; + fmt++; + break; + default: + size = sizeof(int); + break; + } + + 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, unsigned int), 10); + break; +#if INT_MAX != LONG_MAX + case sizeof(long): + 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, unsigned int), 16); + break; +#if INT_MAX != LONG_MAX + case sizeof(long): + 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, unsigned int), 2); + break; +#if INT_MAX != LONG_MAX + case sizeof(long): + d_ultoa(va_arg(va, long), 2); + break; +#endif + } + break; + case 'p': + if (is_far) { + void __far *p = va_arg(va, void __far *); + d_utoa(FP_SEG(p), 16); + dputc(':'); + d_utoa(FP_OFF(p), 16); + } else { + d_utoa((unsigned) va_arg(va, void*), 16); + } + break; + case 's': + d_printstr(is_far ? va_arg(va, const char __far *) + : va_arg(va, const char *), + precision); + break; + case 'c': + dputc(va_arg(va, char)); + break; + case '%': + dputc('%'); + break; + } + fmt++; + } else { + dputc(c); + } + } + + va_end(va); +} + +static void dputs(const char *s) +{ + char c; + while ((c = *(s++))) { + dputc(c); + } + dputc('\n'); +} + +#else /* ENABLE_DLOG */ + +#define dlog_nop() do { } while(0) +#define dlog_init() dlog_nop() +#define dputc(c) dlog_nop() +#define dputs(s) dlog_nop() +#define dprintf(...) dlog_nop() + + +#endif /* ENABLE_DLOG */ + +#endif /* DLOG_H */ diff --git a/inc/dosapi.h b/inc/dosapi.h deleted file mode 100644 index 18dbde0..0000000 --- a/inc/dosapi.h +++ /dev/null @@ -1,182 +0,0 @@ -/* - * VBMouse - Interface to some DOS int 21h services - * Copyright (C) 2022 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef INT21DOS_H -#define INT21DOS_H - -#include -#include -#include -#include -#include "sassert.h" - -#define DOS_PSP_SIZE 256 - -enum dos_error { - DOS_ERROR_SUCCESS = 0, - DOS_ERROR_INVALID_FUNCTION = 1, - DOS_ERROR_FILE_NOT_FOUND = 2, - DOS_ERROR_PATH_NOT_FOUND = 3, - DOS_ERROR_TOO_MANY_OPEN_FILES = 4, - DOS_ERROR_ACCESS_DENIED = 5, - DOS_ERROR_INVALID_HANDLE = 6, - DOS_ERROR_ARENA_TRASHED = 7, - DOS_ERROR_NOT_ENOUGH_MEMORY = 8, - DOS_ERROR_INVALID_BLOCK = 9, - DOS_ERROR_BAD_ENVIRONMENT = 10, - DOS_ERROR_BAD_FORMAT = 11, - DOS_ERROR_INVALID_ACCESS = 12, - DOS_ERROR_INVALID_DATA = 13, - DOS_ERROR_OUT_OF_MEMORY = 14, - DOS_ERROR_INVALID_DRIVE = 15, - DOS_ERROR_CURRENT_DIRECTORY = 16, - DOS_ERROR_NOT_SAME_DEVICE = 17, - DOS_ERROR_NO_MORE_FILES = 18, - DOS_ERROR_WRITE_PROTECT = 19, - DOS_ERROR_BAD_UNIT = 20, - DOS_ERROR_NOT_READY = 21, - DOS_ERROR_BAD_COMMAND = 22, - DOS_ERROR_CRC = 23, - DOS_ERROR_BAD_LENGTH = 24, - DOS_ERROR_SEEK = 25, - DOS_ERROR_NOT_DOS_DISK = 26, - DOS_ERROR_SECTOR_NOT_FOUND = 27, - - DOS_ERROR_GEN_FAILURE = 31, - - DOS_ERROR_HANDLE_EOF = 38, - DOS_ERROR_HANDLE_DISK_FULL = 39, - - DOS_ERROR_FILE_EXISTS = 80, - DOS_ERROR_CANNOT_MAKE = 82, -}; - -// General functions - -static inline void dos_print_char(char c); -#pragma aux dos_print_char = \ - "mov ah, 0x02" \ - "int 0x21" \ - __parm [dl] \ - __modify [ah] - -static inline uint8_t dos_read_char(void); -#pragma aux dos_read_char = \ - "mov ah, 0x08" \ - "int 0x21" \ - __value [al] \ - __modify [ax] - -static inline void dos_print_string(const char *s); -#pragma aux dos_print_string = \ - "mov ah, 0x09" \ - "int 0x21" \ - __parm [dx] \ - __modify [ah] - -static inline uint16_t dos_get_version(void); -#pragma aux dos_get_version = \ - "mov ah, 0x30" \ - "int 0x21" \ - __value [ax] \ - __modify [bx cx] - -// Memory manipulation functions - -typedef __segment segment_t; - -enum dos_allocation_strategy { - DOS_FIT_FIRST = 0, - DOS_FIT_BEST = 1, - DOS_FIT_LAST = 2, - - DOS_FIT_HIGH = 0x80, - DOS_FIT_HIGHONLY = 0x40, -}; - -/** Converts bytes to paragraphs (16 bytes), rounding up. */ -static inline unsigned get_paragraphs(unsigned bytes) -{ - return (bytes + 15) / 16; -} - -static unsigned dos_query_allocation_strategy(void); -#pragma aux dos_query_allocation_strategy = \ - "mov ax, 0x5800" \ - "int 0x21" \ - __value [ax] - -static int dos_set_allocation_strategy(unsigned strategy); -#pragma aux dos_set_allocation_strategy = \ - "clc" \ - "mov ax, 0x5801" \ - "int 0x21" \ - "jc end" \ - "mov ax, 0" \ - "end:" \ - __parm [bx] \ - __value [ax] - -static bool dos_query_umb_link_state(void); -#pragma aux dos_query_umb_link_state = \ - "mov ax, 0x5802" \ - "int 0x21" \ - __value [al] - -static int dos_set_umb_link_state(bool state); -#pragma aux dos_set_umb_link_state = \ - "clc" \ - "mov ax, 0x5803" \ - "int 0x21" \ - "jc end" \ - "mov ax, 0" \ - "end:" \ - __parm [bx] \ - __value [ax] - -/** Allocates a new DOS segment. - * @returns either the allocated segment or 0 if any error. */ -static __segment dos_alloc(unsigned paragraphs); -#pragma aux dos_alloc = \ - "clc" \ - "mov ah, 0x48" \ - "int 0x21" \ - "jnc end" \ - "mov ax, 0xB" \ - "end:" \ - __parm [bx] \ - __value [ax] - -/** Frees DOS segment. */ -static void dos_free(segment_t segment); -#pragma aux dos_free = \ - "mov ah, 0x49" \ - "int 0x21" \ - __parm [es] \ - __modify [ax] - -/** Sets a given PSP as the current active process. */ -static void dos_set_psp(segment_t psp); -#pragma aux dos_set_psp = \ - "mov ah, 0x50" \ - "int 0x21" \ - __parm [bx] \ - __modify [ax] - -#endif // INT21DOS_H diff --git a/inc/dosdrv.h b/inc/dosdrv.h deleted file mode 100644 index 0c33c54..0000000 --- a/inc/dosdrv.h +++ /dev/null @@ -1,319 +0,0 @@ -#ifndef DOSDRV_H -#define DOSDRV_H - -#include -#include "bpb.h" -#include "sassert.h" - -typedef enum DOS_DD_ERROR -{ - DOS_DD_ERR_WRITE_PROTECT = 0, - DOS_DD_ERR_UNKNOWN_UNIT = 1, - DOS_DD_ERR_DRIVE_NOT_READY = 2, - DOS_DD_ERR_UNKNOWN_COMMAND = 3, - DOS_DD_ERR_CRC = 4, - DOS_DD_ERR_BAD_DRS_LEN = 5, - DOS_DD_ERR_SEEK = 6, - DOS_DD_ERR_UNKNOWN_MEDIA = 7, - DOS_DD_ERR_SECTOR_NOT_FOUND = 8, - DOS_DD_ERR_PRINTER_OUT_OF_PAPER = 9, - DOS_DD_ERR_WRITE_FAULT = 10, - DOS_DD_ERR_READ_FAULT = 11, - DOS_DD_ERR_GENERAL_FAILURE = 12, - DOS_DD_ERR_MEDIA_UNAVAILABLE = 0xE, - DOS_DD_ERR_INVALID_DISK_CHANGE = 0xF -} DOS_DD_ERROR; - -typedef enum DOS_DD_STATUS { - DOS_DD_STATUS_DONE = 1 << 8, - DOS_DD_STATUS_BUSY = 1 << 9, - DOS_DD_STATUS_ERROR = 1 << 15, - - DOS_DD_STATUS_ERROR_DONE = DOS_DD_STATUS_ERROR | DOS_DD_STATUS_DONE -} DOS_DD_STATUS; - -typedef enum DOS_DD_MEDIAQUERY_RESULT { - DOS_DD_MEDIAQUERY_CHANGED = -1, - DOS_DD_MEDIAQUERY_UNKNOWN = 0, - DOS_DD_MEDIAQUERY_NOT_CHANGED = 1 -} DOS_DD_MEDIAQUERY_RESULT; - -typedef _Packed struct DOS_DRS -{ - /** The length of this structure. */ - uint8_t len; - /** The subunit the operation is for. */ - uint8_t unit; - - uint8_t function; - - uint16_t status; - - uint32_t reserved1; - uint32_t reserved2; - - union { - union { - struct { - uint8_t reserved; - /** End of memory available for driver. */ - void __far * avail_addr; - /** Command line (i.e. the DEVICE= line used to load this driver. ) */ - const char __far * cmdline; - /** First drive that is going to be allocated for this driver. */ - uint8_t first_drive_number; - } in; - struct { - /** Number of units/drive letters to allocate for this driver. */ - uint8_t number_units; - /** Break addr: everything after this address is deallocated after init. */ - void __far * break_addr; - /** Pointer to the BPB array. */ - DOS_BPB * __far * bpbs; - uint8_t reserved; - /** Display error message on failure. */ - uint16_t error_msg_flag; - } out; - } init; - union { - struct { - /** Media descriptor of the current media. */ - uint8_t cur_media_desc; - } in; - struct { - uint8_t reserved; - uint8_t result; - /** Volume ID of previous loaded media. */ - char far * volume_id; - } out; - } mediacheck; - union { - struct { - /** Media descriptor of the current media. */ - uint8_t cur_media_desc; - /** Buffer with sector 0 of the disk, depending on driver flags */ - void __far * sector_zero; - } in; - struct { - uint8_t reserved; - void __far * sector_zero; - /** Address for the new BPB. */ - DOS_BPB __far * bpb; - } out; - } buildbpb; - struct { - uint8_t data; - /** Input: pointer to buffer. */ - void __far * buffer; - /** Input/Output: buffer len / actual len */ - uint16_t len; - } ioctlrw; - struct { - /** Input: current media descriptor byte. */ - uint8_t cur_media_desc; - /** Input: pointer to buffer. */ - void __far * buffer; - /** Input: Number of sectors to read/write. Output: number sectors actually read/written. */ - uint16_t len; - /** Input: Begin sector. Or, if 0xFFFF, see start_sector_huge. */ - uint16_t start_sector; - /** Output: Current Volume ID. */ - char far * volume_id; - uint32_t start_sector_huge; - } rw; - struct { - uint8_t category; - uint8_t minor; - uint32_t reserved; - void __far * data; - } ioctl; - } cmd; -} DOS_DRS; - -typedef void (DOS_DD_STRATEGY)(DOS_DRS __far *drs); -typedef void (DOS_DD_INTERRUPT)(DOS_DRS __far *drs); -#pragma aux DOS_DD_STRATEGY __parm __caller [es bx] __modify [] __loadds __far -#pragma aux DOS_DD_INTERRUPT __parm __caller [] __modify [] __loadds __far - -typedef enum DOS_DD_FUNCTIONS -{ - DOS_DD_FUNC_INIT = 0, - DOS_DD_FUNC_MEDIA_CHECK = 1, - DOS_DD_FUNC_BUILD_BPB = 2, - DOS_DD_FUNC_IOCTL_READ = 3, - DOS_DD_FUNC_READ = 4, - DOS_DD_FUNC_NONDESTRUCTIVE_READ = 5, - DOS_DD_FUNC_INPUT_STATUS = 6, - DOS_DD_FUNC_INPUT_FLUSH = 7, - DOS_DD_FUNC_WRITE = 8, - DOS_DD_FUNC_WRITE_VERIFY = 9, - DOS_DD_FUNC_OUTPUT_STATUS = 10, - DOS_DD_FUNC_OUTPUT_FLUSH = 11, - DOS_DD_FUNC_IOCTL_WRITE = 12, - DOS_DD_FUNC_DEVICE_OPEN = 13, - DOS_DD_FUNC_DEVICE_CLOSE = 14, - DOS_DD_FUNC_REMOVEABLE_MEDIA = 15, - DOS_DD_FUNC_OUTPUT_UNTIL_BUSY = 16, - DOS_DD_FUNC_IOCTL_GENERIC = 19, - DOS_DD_FUNC_GET_LOGICAL_DEVICE = 23, - DOS_DD_FUNC_SET_LOGICAL_DEVICE = 24, - DOS_DD_FUNC_IOCTL_QUERY = 25, -} DOS_DD_COMMAND; - - -/** Device driver (.sys) header */ -typedef _Packed struct DOS_DD_HEADER -{ - void __far *nextDevice; - uint16_t attributes; - DOS_DD_STRATEGY *strategyCb; - DOS_DD_INTERRUPT *interrupCb; - char name[8]; -} DOS_DD_HEADER; - -typedef enum DOS_DD_ATTRIBUTES -{ - /** Character-device driver. */ - DOS_DD_CHAR = 1 << 15, - /** Block-device driver. */ - DOS_DD_BLOCK = 0, - - /** Supports IOCTL Read/Write funcs */ - DOS_DD_SUPPORT_IOCTL_RW = 1 << 14, - - /** Support IOCTL query funcs. */ - DOS_DD_SUPPORT_IOCTL_QUERY = 1 << 7, - - // Character-device specific - DOS_CHAR_CURRENT_STDIN_DEVICE = 1 << 0, - DOS_CHAR_CURRENT_STDOUT_DEVICE = 1 << 1, - DOS_CHAR_CURRENT_NUL_DEVICE = 1 << 2, - DOS_CHAR_CURRENT_CLOCK_DEVICE = 1 << 3, - DOS_CHAR_SPECIAL_FAST_CONSOLE_IO = 1 << 4, - DOS_CHAR_OUTPUT_UNTIL_BUSY = 1 << 13, - - // Block-device specific - /** The driver support 32-bit sector address. (Can use the _huge fields. ) */ - DOS_BLOCK_SUPPORT_32BIT_ADDRESSES = 1 << 1, - - /** Support Get/Set Logical Drive and generic IOCTL funcs. */ - DOS_BLOCK_SUPPORT_IOCTL_GENERIC = 1 << 6, - - /** Supports Open/Close/Removable media funcs */ - DOS_BLOCK_SUPPORT_REMOVABLE_MEDIA = 1 << 11, - - /** if set, DOS not read the 1st sector of the 1st FAT during build bpb. */ - DOS_BLOCK_FORMAT_NON_IBM = 1 << 13, -} DOS_DD_ATTRIBUTES; - -// Generic IOCTLs - -typedef enum DOS_IOCTL_CATEGORY -{ - DOS_IOCTL_SERIAL = 0x1, - DOS_IOCTL_CONSOLE = 0x3, - DOS_IOCTL_PARALLEL = 0x5, - DOS_IOCTL_DISK = 0x8, -} DOS_IOCTL_CATEGORY; - -typedef enum DOS_IOCTL_MINOR -{ - DOS_IOCTL_CONSOLE_SET_ITERATION_COUNT = 0x45, - DOS_IOCTL_CONSOLE_SELECT_CODE_PAGE = 0x4A, - DOS_IOCTL_CONSOLE_START_CODE_PAGE_PREPARE = 0x4C, - DOS_IOCTL_CONSOLE_END_CODE_PAGE_PREPARE = 0x4D, - DOS_IOCTL_CONSOLE_GET_IERATION_COUNT = 0x65, - DOS_IOCTL_CONSOLE_QUERY_SELECTED_CODE_PAGE = 0x6A, - DOS_IOCTL_CONSOLE_QUERY_CODE_PAGE_PREPARE_LIST = 0x6B, - - DOS_IOCTL_DISK_SET_DEVICE_PARAMETERS = 0x40, - DOS_IOCTL_DISK_WRITE_TRACK_ON_LOGICAL_DEVICE = 0x41, - DOS_IOCTL_DISK_FORMAT_TRACK_ON_LOGICAL_DEVICE = 0x42, - DOS_IOCTL_DISK_SET_MEDIA_ID = 0x46, - DOS_IOCTL_DISK_GET_DEVICE_PARAMETERS = 0x60, - DOS_IOCTL_DISK_READ_TRACK_ON_LOGICAL_DEVICE = 0x61, - DOS_IOCTL_DISK_VERIFY_TRACK_ON_LOGICAL_DEVICE = 0x62, - DOS_IOCTL_DISK_GET_MEDIA_ID = 0x66, - DOS_IOCTL_DISK_SENSE_MEDIA_TYPE = 0x68, -} DOS_IOCTL_MINOR; - - -// Block-device / disk data structures - -typedef _Packed struct DOS_DEVICEPARAMS -{ - uint8_t specialFunctions; - uint8_t deviceType; - uint16_t deviceAttributes; - uint16_t numberOfCylinders; - uint8_t mediaType; - DOS_BPB_DOS3 bpb; -} DOS_DEVICEPARAMS; - -enum DOS_DEVICEPARAMS_SPECIAL_FUNCTIONS -{ - /** Use the device BPB for all subsequent Build BPB requests. */ - DOS_DEVICEPARAMS_USE_DEVICE_BPB = (1 << 0), - /** Ignore all fields except the TRACKLAYOUT structure appended after DEVICEPARAMS */ - DOS_DEVICEPARAMS_TRACKLAYOUT = (1 << 1), - /** The sectors in the track are all the same size, and sector numbers are in the range [1..N] - * where N is the total number of sectors in the track. */ - DOS_DEVICEPARAMS_REGULARTRACKS = (1 << 2), -}; - -enum DOS_DEVICEPARAMS_DEVICE_TYPE -{ - DOS_DEVICEPARAMS_320_360K = 0x0, - DOS_DEVICEPARAMS_1_2_MB = 0x1, - DOS_DEVICEPARAMS_720K = 0x2, - DOS_DEVICEPARAMS_8INCH_SD = 0x3, - DOS_DEVICEPARAMS_8INCH_DD = 0x4, - DOS_DEVICEPARAMS_HDD = 0x5, - DOS_DEVICEPARAMS_TAPE = 0x6, - DOS_DEVICEPARAMS_1_44_MB = 0x7, - DOS_DEVICEPARAMS_OPTICAL = 0x8, - DOS_DEVICEPARAMS_2_88_MB = 0x9 -}; - -enum DOS_DEVICEPARAMS_DEVICE_ATTR -{ - DOS_DEVICEPARAMS_REMOVABLE = 0, - DOS_DEVICEPARAMS_FIXED = 1, - DOS_DEVICEPARAMS_HAS_DOORLOCK = 1 << 1 -}; - -typedef _Packed struct DOS_MEDIA_ID -{ - /** Must be zero. */ - uint16_t infoLevel; - uint32_t serialNumber; - char volumeLabel[11]; - char fsType[8]; -} DOS_MEDIA_ID; - -typedef _Packed struct DOS_RWBLOCK -{ - uint8_t specialFunctions; - uint16_t head; - uint16_t cylinder; - uint16_t firstSector; - uint16_t sectors; - void far * buffer; -} DOS_RWBLOCK; - -typedef _Packed struct DOS_FVBLOCK -{ - uint8_t specialFunctions; - uint16_t head; - uint16_t cylinder; -} DOS_FVBLOCK; - -typedef _Packed union DOS_IOCTL_DISK_DATA -{ - DOS_DEVICEPARAMS devparams; - DOS_MEDIA_ID mid; - DOS_FVBLOCK fv; - DOS_RWBLOCK rw; -} DOS_IOCTL_DISK_DATA; - -#endif // DOSSYS_H diff --git a/inc/dosint.h b/inc/dosint.h deleted file mode 100644 index cfa855d..0000000 --- a/inc/dosint.h +++ /dev/null @@ -1,250 +0,0 @@ -#ifndef DOSINT_H -#define DOSINT_H - -// Internal DOS structures - -// For documentation of these, better grab "Undocumented DOS" or similar book -// These are probably valid only on DOS >= 4 - -typedef _Packed struct dos_current_directory_structure { - char curr_path[67]; - uint16_t flags; - uint32_t disk_blk; - void __far * info; - char pad1[11]; -} DOSCDS; -STATIC_ASSERT(sizeof(DOSCDS) == 88); - -enum dos_cds_flags { - DOS_CDS_FLAG_PHYSICAL = 0x4000, - DOS_CDS_FLAG_NETWORK = 0x8000 -}; - -typedef _Packed struct dos_system_file_table_entry { - uint16_t num_handles; - uint16_t open_mode; - uint8_t attr; - uint16_t dev_info; - void __far *dpb; - uint16_t start_cluster; - uint16_t f_time; - uint16_t f_date; - uint32_t f_size; - uint32_t f_pos; - uint16_t last_rel_cluster; - uint16_t last_abs_cluster; - uint16_t dir_sector; - uint8_t dir_entry; - char filename[11]; -} DOSSFT; -STATIC_ASSERT(sizeof(DOSSFT) == 43); - -enum dos_sft_dev_flags { - DOS_SFT_FLAG_NETWORK = 0x8000, - DOS_SFT_FLAG_TIME_SET = 0x4000, - DOS_SFT_FLAG_CLEAN = 0x0040, - DOS_SFT_DRIVE_MASK = 0x001F -}; - -typedef _Packed struct dos_search_data_block { - uint8_t drive; - char search_templ[11]; - uint8_t search_attr; - uint16_t dir_entry; - uint16_t par_clstr; - char pad1[4]; -} DOSSDB; -STATIC_ASSERT(sizeof(DOSSDB) == 21); - -enum dos_sdb_drive_flags { - DOS_SDB_DRIVE_FLAG_NETWORK = 0x80, - DOS_SDB_DRIVE_MASK = 0x1F -}; - -typedef _Packed struct dos_lock_params { - uint32_t start_offset; - uint32_t region_size; -} DOSLOCK; - -typedef _Packed struct dos_direntry { - char filename[11]; - uint8_t attr; - char pad1[10]; - uint16_t f_time; - uint16_t f_date; - uint16_t start_cluster; - uint32_t f_size; -} DOSDIR; -STATIC_ASSERT(sizeof(DOSDIR) == 32); - -typedef _Packed struct dos_swappable_area { - uint8_t criterr; - uint8_t indos; - uint8_t drive_num; - uint8_t lasterr_dum[9]; - uint8_t __far *cur_dta; - uint16_t cur_psp; - uint8_t pad1[4]; - uint8_t cur_drive; - uint8_t pad2[135]; - char fn1[128]; - char fn2[128]; - DOSSDB sdb; - DOSDIR found_file; - DOSCDS drive_cdscopy; - uint8_t fcb_fn1[11]; - uint8_t pad3[1]; - uint8_t fcb_fn2[11]; - uint8_t pad4[11]; - uint8_t search_attr; - uint8_t open_mode; - uint8_t pad5[51]; - DOSCDS __far *drive_cds; - uint8_t pad6[12]; - uint16_t fn1_csoffset; - uint16_t fn2_csoffset; - uint8_t pad7[71]; - uint16_t openex_act; - uint16_t openex_attr; - uint16_t openex_mode; - uint8_t pad8[29]; - DOSSDB rename_srcfile; - DOSDIR rename_file; -} DOSSDA; -STATIC_ASSERT(offsetof(DOSSDA, cur_dta) == 0xC); -STATIC_ASSERT(offsetof(DOSSDA, cur_drive) == 0x16); -STATIC_ASSERT(offsetof(DOSSDA, fn1) == 0x9E); -STATIC_ASSERT(offsetof(DOSSDA, fn2) == 0x11E); -STATIC_ASSERT(offsetof(DOSSDA, sdb) == 0x19E); -STATIC_ASSERT(offsetof(DOSSDA, found_file) == 0x1B3); -STATIC_ASSERT(offsetof(DOSSDA, search_attr) == 0x24D); -STATIC_ASSERT(offsetof(DOSSDA, open_mode) == 0x24E); -STATIC_ASSERT(offsetof(DOSSDA, drive_cds) == 0x282); -STATIC_ASSERT(offsetof(DOSSDA, openex_act) == 0x2DD); -STATIC_ASSERT(offsetof(DOSSDA, openex_mode) == 0x2E1); - -typedef _Packed struct dos_list_of_lists { - char pad1[22]; - DOSCDS __far *cds; - char pad2[7]; - uint8_t last_drive; -} DOSLOL; - -typedef _Packed struct dos_nls_table { - uint8_t table_id; - void __far *table_data; -} NLSTABLE; - -typedef _Packed struct file_char_table { - uint16_t size; // table size (not counting this word) - uint8_t unk1; // ??? (01h for MS-DOS 3.30-6.00) - uint8_t lowest; // lowest permissible character value for filename - uint8_t highest; // highest permissible character value for filename - uint8_t unk2; // ??? (00h for MS-DOS 3.30-6.00) - uint8_t first_x; // first excluded character in range \ all characters in this - uint8_t last_x; // last excluded character in range / range are illegal - uint8_t unk3; // ??? (02h for MS-DOS 3.30-6.00) - uint8_t n_illegal; // number of illegal (terminator) characters - uint8_t illegal[1]; // characters which terminate a filename: ."/\[]:|<>+=;, -} FCHAR; - -static inline int drive_letter_to_index(char letter) -{ - if (letter >= 'A' && letter <= 'Z') return letter - 'A'; - else if (letter >= 'a' && letter <= 'z') return letter - 'a'; - else return -1; -} - -static inline char drive_index_to_letter(int index) -{ - return 'A' + index; -} - -static inline DOSSDA __far * dos_get_swappable_dos_area(void); -#pragma aux dos_get_swappable_dos_area = \ - "push ds" \ - "mov ax, 0x5D06" \ - "int 0x21" \ - "jnc success" \ - "xor si, si" \ - "mov es, si" \ - "jmp end" \ - "success: mov ax, ds" \ - "mov es, ax" \ - "end: pop ds" \ - __value [es si] \ - __modify [ax cx dx] - -static inline DOSLOL __far * dos_get_list_of_lists(void); -#pragma aux dos_get_list_of_lists = \ - "mov ax, 0x5200" \ - "int 0x21" \ - __value [es bx] \ - __modify [ax] - -static inline uint16_t dos_sft_decref(DOSSFT __far *sft); -#pragma aux dos_sft_decref = \ - "mov ax, 0x1208" \ - "int 0x2F" \ - __parm [es di] \ - __value [ax] - -// Network redirector interface - -enum DOS_REDIR_SUBFUNCTION { - DOS_FN_RMDIR = 0x01, - DOS_FN_MKDIR = 0x03, - DOS_FN_CHDIR = 0x05, - DOS_FN_CLOSE = 0x06, - DOS_FN_COMMIT = 0x07, - DOS_FN_READ = 0x08, - DOS_FN_WRITE = 0x09, - DOS_FN_LOCK = 0x0A, - DOS_FN_UNLOCK = 0x0B, - DOS_FN_GET_DISK_FREE = 0x0C, - DOS_FN_SET_FILE_ATTR = 0x0E, - DOS_FN_GET_FILE_ATTR = 0x0F, - DOS_FN_RENAME = 0x11, - DOS_FN_DELETE = 0x13, - DOS_FN_OPEN = 0x16, - DOS_FN_CREATE = 0x17, - DOS_FN_FIND_FIRST = 0x1B, - DOS_FN_FIND_NEXT = 0x1C, - DOS_FN_CLOSE_ALL = 0x1D, - DOS_FN_DO_REDIR = 0x1E, - DOS_FN_PRINTSETUP = 0x1F, - DOS_FN_FLUSH = 0x20, - DOS_FN_SEEK_END = 0x21, - DOS_FN_ATEXIT = 0x22, - DOS_FN_QUALIFY = 0x23, - DOS_FN_OPEN_EX = 0x2E -}; - -enum OPENEX_ACTIONS { - OPENEX_FAIL_IF_EXISTS = 0x00, - OPENEX_OPEN_IF_EXISTS = 0x01, - OPENEX_REPLACE_IF_EXISTS = 0x02, - OPENEX_FAIL_IF_NEW = 0x00, - OPENEX_CREATE_IF_NEW = 0x10, -}; - -enum OPENEX_MODE { - OPENEX_MODE_READ = 0, - OPENEX_MODE_WRITE = 1, - OPENEX_MODE_RDWR = 2, - OPENEX_MODE_MASK = 3, - OPENEX_SHARE_COMPAT = 0x00, - OPENEX_SHARE_DENYALL = 0x10, - OPENEX_SHARE_DENYWRITE = 0x20, - OPENEX_SHARE_DENYREAD = 0x30, - OPENEX_SHARE_DENYNONE = 0x40, - OPENEX_SHARE_MASK = 0x70, -}; - -enum OPENEX_RESULT { - OPENEX_FILE_OPENED = 1, - OPENEX_FILE_CREATED = 2, - OPENEX_FILE_REPLACED = 3, -}; - -#endif // DOSINT_H diff --git a/inc/mbr.h b/inc/mbr.h deleted file mode 100644 index d45727d..0000000 --- a/inc/mbr.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef MBR_H -#define MBR_H - -#include "sassert.h" - -enum MBR_PARTTYPE -{ - MBR_EMPTY = 0, - MBR_FAT12 = 0x01, - MBR_FAT16_32MB = 0x04, - MBR_EXT = 0x05, - MBR_FAT16 = 0x06, - MBR_IFS = 0x07, - MBR_FAT32 = 0x0B, - MBR_FAT32_LBA = 0x0C, - MBR_FAT16_LBA = 0x0E, - MBR_EXT_LBA = 0x0F, - MBR_LINUX_SWAP = 0x82, - MBR_LINUX = 0x83, -}; - -typedef _Packed struct MBR_CHS -{ - uint8_t head; - unsigned sector : 6; - unsigned cyl : 10; -} MBR_CHS; - -STATIC_ASSERT(sizeof(MBR_CHS) == 3); - -typedef _Packed struct MBR_PARTENTRY -{ - uint8_t status; - MBR_CHS chsFirst; - uint8_t type; - MBR_CHS chsLast; - uint32_t lbaFirst; - uint32_t lbaLen; -} MBR_PARTENTRY; - -STATIC_ASSERT(sizeof(MBR_PARTENTRY) == 16); - -#define MBR_PARTENTRY_OFFSET_ON_DISK 446 -#define MBR_SIGNATURE_OFFSET_ON_DISK 510 - -#endif // MBR_H 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 - #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 diff --git a/inc/usb.h b/inc/usb.h new file mode 100644 index 0000000..3ea18ad --- /dev/null +++ b/inc/usb.h @@ -0,0 +1,45 @@ +/* + * CHU - driver for CH375 in disk mode + * Copyright (C) 2024 Javier S. Pedro + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#ifndef USB_H +#define USB_H + +typedef enum usb_descriptor_type { + USB_DESCRIPTOR_TYPE_DEVICE = 1, + USB_DESCRIPTOR_TYPE_CONFIG = 2 +} usb_descriptor_type; + +typedef struct usb_device_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; +} _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 . + */ + +#ifndef CH375LNX_H +#define CH375LNX_H + +#include +#include +#include +#define _Packed __attribute__ ((packed)) +typedef enum { + false = 0, + true = 1 +} bool; + +#include + +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 . + */ + +/** 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 + +#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 . + */ + +#ifdef MODVERSIONS +#include +#endif + +#include + +#define MAKE_VERSION_CODE(vers, rel, seq) ( (vers)<<16 | (rel)<<8 | (seq) ) + +#include +#include + +// 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 +#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 +#include +#include + +#include +#include +#include + +#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 + +// 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 +#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 diff --git a/linux/makefile b/linux/makefile new file mode 100644 index 0000000..f293cc1 --- /dev/null +++ b/linux/makefile @@ -0,0 +1,28 @@ +MODDIR := /lib/modules/$(shell uname -r) + +INCLUDE := -isystem $(MODDIR)/build/include -isystem ../inc +CFLAGS := -O2 -D__KERNEL__ -DMODULE -Wall $(INCLUDE) +ifneq (,$(wildcard $(MODDIR)/build/include/linux/modversions.h)) +CFLAGS += -DMODVERSIONS +endif + +CC ?= gcc + +chudisk.o: chudisk.c lnxabs.h ch375lnx.h + +.PHONY: clean install uninstall test +clean: + rm -rf *.o + +install: chudisk.o + install -m0644 chudisk.o $(MODDIR)/block + depmod -A + +uninstall: + rm -f $(MODDIR)/block/chudisk.o + +test: chudisk.o + if lsmod | grep chudisk; then rmmod chudisk; fi + dmesg -n 8 + dmesg -c > /dev/null + insmod chudisk.o force=1 diff --git a/linux/mkdev.sh b/linux/mkdev.sh new file mode 100755 index 0000000..d1393e6 --- /dev/null +++ b/linux/mkdev.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +major=53 +if [ $# -gt 0 ]; then + major=$1 +fi + +prefix=/dev/chuda +if [ $# -gt 1 ]; then + prefix=$2 +fi + +rm -f ${prefix}* +mknod "${prefix}" b $major 0 +for i in $(seq 1 20); do + mknod "${prefix}${i}" b $major $i +done + +chown root:disk ${prefix}* diff --git a/makefile b/makefile index a42e957..c7cc4db 100644 --- a/makefile +++ b/makefile @@ -1,67 +1,45 @@ #!/usr/bin/env wmake -# All binaries to build -bins = test.exe chudisk.sys chud.exe +subdirs=dos w9x -# Object files for chdisk -chudisk_objs = chudisk.obj chudinit.obj cio.obj - -# Compiler arguments for DOS -dos_cflags = -bt=dos -3 -ms -osi -ox -w3 -wcd=202 -g=DGROUP -# -ms to use small memory model (this assumes ss == ds) -# -osi to optimize for size, put intrinsics inline (to avoid runtime calls) -# -ox extra optimization (impact is not very big) -# -w3 enables warnings -# -wcd=202 disables the unreferenced function warning (e.g., for inline functions in headers) - -# Compiler arguments for the DOS drivers -dosdrv_cflags = -bt=dos -3 -ms -zu -s -osi -ox -w3 -wcd=202 -g=DGROUP -# -zu since ss != ds in the driver -# -s to disable stack checks, since it inserts calls to the runtime - -# Full compiler command lines -compile_dos = *wcc -fo=$^@ $(dos_cflags) $[@ -compile_dosdrv = *wcc -fo=$^@ $(dosdrv_cflags) $[@ - -# Object files - -.BEFORE: - # We need DOS and Windows headers, not host platform's - set include=$(%watcom)/h/win;$(%watcom)/h - -all: $(bins) .SYMBOLIC - -test.exe: test.obj .AUTODEPEND - *wlink system dos name $@ file { test.obj } - -test.obj: test.c .AUTODEPEND - *wcc -fo=$^@ -bt=dos -3 -osi -w3 -wcd=202 $[@ - -chudisk.sys: chudisk.lnk $(chudisk_objs) .AUTODEPEND - *wlink @$[@ name $@ file { $(chudisk_objs) } - -chudisk.obj: chudisk.c .AUTODEPEND - $(compile_dosdrv) - -chudinit.obj: chudinit.c .AUTODEPEND - $(compile_dosdrv) - -cio.obj: cio.c .AUTODEPEND - $(compile_dosdrv) - -chud.exe: chud.obj .AUTODEPEND - *wlink name $@ system dos file { chud.obj } - -chud.obj: chud.c .AUTODEPEND - $(compile_dos) +all: .SYMBOLIC + for %i in ($(subdirs)) do %make %i/$^@ clean: .SYMBOLIC - rm -f $(bins) *.obj + for %i in ($(subdirs)) do %make %i/$^@ + +# This would be so much easier with a multi-line macro... +sd1=cd $^: +!ifdef debug +sd2=wmake $^. debug=$(debug) +!else +sd2=wmake $^. +!endif +sd3=cd .. + +dos/all: .SYMBOLIC + $(sd1) + $(sd2) + $(sd3) +dos/clean: .SYMBOLIC + $(sd1) + $(sd2) + $(sd3) +w9x/all: .SYMBOLIC + $(sd1) + $(sd2) + $(sd3) +w9x/clean: .SYMBOLIC + $(sd1) + $(sd2) + $(sd3) # Build a zip with the driver binaries -zip: $(bins) .SYMBOLIC - zip --DOS-names -fz- chu.zip $(bins) +zip: dos/all w9x/all .SYMBOLIC + zip --DOS-names -fz- chu.zip dos/chudisk.sys dos/chud.exe w9x/chudisk.pdr w9x/chudisk.inf srczip: .SYMBOLIC - zip -fz- chusrc.zip COPYING README.md makefile *.lnk *.h *.c inc/*.h - + zip -fz- chusrc.zip COPYING README.md makefile inc/*.h & + dos/makefile dos/*.c dos/*.h dos/*.lnk & + w9x/makefile w9x/*.c w9x/*.h w9x/*.lnk w9x/*.inf w9x/inc/*.h & + linux/makefile linux/*.c linux/*.h linux/*.sh diff --git a/test.c b/test.c deleted file mode 100644 index 3cb2ffd..0000000 --- a/test.c +++ /dev/null @@ -1,489 +0,0 @@ -#include -#include -#include -#include - -#include "usb.h" -#include "ch375.h" - -unsigned short ch375_port = 0x260; - -#define SWAPNC_32(w) (\ - (((w) & 0x000000FFUL) << 24) |\ - (((w) & 0x0000FF00UL) << 8) |\ - (((w) & 0x00FF0000UL) >> 8) |\ - (((w) & 0xFF000000UL) >> 24)\ - ) - -static uint8_t buffer[64]; - -typedef uint16_t Centisecs; -#define CENTISECS_MAX (60 * 100U) - -/// Gets the current wallclock centisecond (1/100 of a second). -// Note this wraps around every minute, so max value is CENTISECS_MAX. -static Centisecs get_centisecs() -{ - struct dostime_t now; - _dos_gettime(&now); - return (now.second * 100U) + now.hsecond; -} - -static inline Centisecs centisecs_remaining(Centisecs start, Centisecs deadline) -{ - Centisecs now = get_centisecs(); - if (now < start) { - // Rollover happened - now += CENTISECS_MAX; - } - if (now >= deadline) { - return 0; - } else { - return deadline - now; - } -} - -static void delay_centisecs(Centisecs timeout) -{ - Centisecs start = get_centisecs(); - Centisecs deadline = start + timeout; - while (centisecs_remaining(start, deadline)) { - printf("Delay deadline=%u now=%u\n", deadline, get_centisecs()); - } -} - - -typedef bool (*PollFunction)(void); - -static bool wait_with_timeout(Centisecs timeout, PollFunction pf) { - Centisecs start = get_centisecs(); - 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(); - } - } - if (pf()) { - printf("Wait OK, %d centisecs elapsed\n", get_centisecs() - start); - return true; - } else { - printf("Wait timeout\n"); - return false; - } - //return pf(); -} - -static bool poll_test_connect(void) { - return ch375_read_data() != 0; -} - - -static bool wait_interrupt_with_timeout(unsigned int timeout) { - return wait_with_timeout(timeout, ch375_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()); - // Above also clears pending ints... -} - -void chip_reset() -{ - printf("Chip reset\n"); - ch375_reset(); - delay_centisecs(5); -} - -void chip_sleep() -{ - printf("Chip sleep\n"); - ch375_enter_sleep(); -} - -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) { - printf("Set usb mode failed\n"); - } -} - -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) { - printf("Set usb mode reset failed\n"); - } -} - -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) { - printf("Set usb mode failed\n"); - return false; - } - - dump_int_status(); - - return true; -} - -bool usb_host_connect(void) -{ - uint8_t status; - - printf("Test connect...\n"); - ch375_test_connect(); - if (!wait_with_timeout(1, poll_test_connect)) { - printf("Test connect timedout\n"); - } - status = ch375_read_data(); - printf("Test connect status = 0x%hx\n", status); - switch (status) { - case CH375_INT_USB_CONNECT: - printf("USB device connected\n"); - return true; - case CH375_INT_USB_READY: - printf("USB device ready\n"); - return true; - case CH375_INT_USB_DISCONNECT: - printf("USB device not connected\n"); - default: - return false; - } -} - -static int usb_host_wait_int_read_data(Centisecs timeout, uint8_t buffer[]) -{ - uint8_t status; - if (!wait_with_timeout(timeout, ch375_poll_interrupt)) { - return -1; - } - - // Get and acknowledge interrupt - status = ch375_get_int_status(); - 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); -} - -static bool init_disk() -{ - uint8_t status; - - printf("Disk init...\n"); - ch375_disk_init(); - if (!wait_interrupt_with_timeout(100)) { - printf("Disk init timeout\n"); - } - status = ch375_get_int_status(); - printf("Disk init status = 0x%hx\n", status); - - return status == CH375_INT_USB_SUCCESS; -} - -static bool disk_ready() -{ - uint8_t status; - ch375_test_disk_ready(); - if (!wait_interrupt_with_timeout(100)) { - printf("Disk ready timeout\n"); - } - status = ch375_get_int_status(); - printf("Disk ready status = 0x%hx\n", status); - return status == CH375_INT_USB_SUCCESS; -} - -static void disk_size() -{ - uint8_t status; - - printf("Disk size...\n"); - ch375_send_command(CH375_DISK_SIZE); - if (!wait_interrupt_with_timeout(100)) { - printf("Disk size timeout\n"); - } - status = ch375_get_int_status(); - printf("Disk size status = 0x%hx\n", status); - - if (status == CH375_INT_USB_SUCCESS) { - int len; - - ch375_send_command(CH375_RD_USB_DATA); - - len = ch375_read_data_block(buffer); - printf("Read %d bytes\n", len); - - if (len >= 8) { - uint32_t sectors = SWAPNC_32(*(uint32_t*)&buffer[0]), - bytes_per_sector = SWAPNC_32(*(uint32_t*)&buffer[4]); - uint32_t kbytes = sectors / 2; - uint32_t mbytes = kbytes / 1024; - - printf("%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]); - printf("%lu sectors, %lu bytes per sector, %u MiB\n", sectors, bytes_per_sector, mbytes); - } - } -} - -int disk() -{ - bool ready = disk_ready(); - int i = 0; - - if (ready) { - printf("Disk ready\n"); - } else { - printf("Disk not ready\n"); - - if (!usb_host_connect()) { - return EXIT_FAILURE; - } - - if (!init_disk()) { - return EXIT_FAILURE; - } - - i = 3; - while (i >= 0 && !(ready = disk_ready())) { - i--; - printf("Give more time..\n"); - } - - if (ready) { - printf("Disk now ready\n"); - } else { - printf("Disk NOT ready\n"); - return EXIT_FAILURE; - } - } - - dump_int_status(); - - disk_size(); - - return EXIT_SUCCESS; -} - -int disk_read(uint32_t lba) -{ - uint8_t status; - unsigned total = 0; - unsigned i; - ch375_disk_read(lba, 1); - if (!wait_interrupt_with_timeout(100)) { - printf("Disk read timeout\n"); - } - while ((status = ch375_get_int_status() == CH375_INT_USB_DISK_READ)) { - int len; - ch375_send_command(CH375_RD_USB_DATA); - - if (total >= 512) { - printf("Read buffer overflow!"); - break; - } - - len = ch375_read_data_block(&buffer[total]); - printf("Disk read data block len = %d\n", len); - if (len > 0) { - total += len; - - ch375_send_command(CH375_DISK_RD_GO); - if (!wait_interrupt_with_timeout(100)) { - printf("Disk read more timeout\n"); - } - } else if (len == 0) { - break; - } - } - printf("Disk read status = 0x%hx\n", status); - - printf("Disk read total bytes = %u\n", total); - - for (i = 512 - (8*4); i < total; i+=8) { - printf("%hx %hx %hx %hx %hx %hx %hx %hx\n", - buffer[i+0], buffer[i+1], buffer[i+2], buffer[i+3], - buffer[i+4], buffer[i+5], buffer[i+6], buffer[i+7]); - } - - return EXIT_SUCCESS; -} - -int autou() -{ - uint8_t status; - -#if 1 - usb_host_reset(); - delay(100); -#endif - -#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); -#endif - -#if 1 - printf("Auto setup...\n"); - - ch375_auto_setup(); - ch375_send_command(CH375_AUTO_SETUP); - if (!wait_interrupt_with_timeout(100)) { - printf("Auto setup timeout\n"); - } - status = ch375_get_int_status(); - printf("Auto setup status = 0x%hx\n", status); - - delay(100); -#endif - - if (status == CH375_INT_USB_SUCCESS) { - printf("Auto setup success\n"); - } else { - printf("Auto setup fail, status=0x%x\n", status); - } - - dump_int_status(); - - return EXIT_SUCCESS; -} - -int host() -{ - uint8_t status; - usb_device_descriptor *dev_descr; - int n; - - //ch375_set_retry(CH375_RETRY_NAK_INFINITE | CH375_RETRY_TIMEOUT_MAX); - -#if 0 - usb_host_reset(); - //delay(100); -#endif - -#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); -#endif - -#if 0 - if (!usb_host_connect()) { - return EXIT_FAILURE; - } -#endif - - dump_int_status(); - - printf("Reading descriptor %d...\n", USB_DESCRIPTOR_TYPE_DEVICE); - ch375_get_descriptor(USB_DESCRIPTOR_TYPE_DEVICE); - n = usb_host_wait_int_read_data(100, buffer); - if (n >= 0) { - printf("Got descriptor 1 size = %d\n", n); - } else { - printf("Failed to get descriptor 1, err=0x%hx\n", -n); - return EXIT_FAILURE; - } - - dump_int_status(); - - dev_descr = (usb_device_descriptor*)buffer; - - 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); - n = usb_host_wait_int_read_data(100, buffer); - if (n >= 0) { - printf("Got descriptor 2 size = %d\n", n); - } else { - printf("Failed to get descriptor 2, err=0x%hx\n", -n); - return EXIT_FAILURE; - } - - dump_int_status(); - - return EXIT_SUCCESS; -} - -int main(int argc, const char *argv[]) -{ - char mode = 'd'; - - if (argc > 1) { - mode = argv[1][0]; - } - - printf("Test mode %c\n", mode); - - dump_int_status(); - - switch (mode) { - case 'd': - return disk(); - case 'e': - return disk_read(0); - case 'a': - return autou(); - case 'm': - return host(); - case 'h': - usb_host_on(); - return EXIT_SUCCESS; - case 'c': - usb_host_connect(); - return EXIT_SUCCESS; - case 'o': - usb_host_off(); - return EXIT_SUCCESS; - case 'b': - usb_host_reset(); - return EXIT_SUCCESS; - case 's': - chip_sleep(); - return EXIT_SUCCESS; - case 'r': - chip_reset(); - return EXIT_SUCCESS; - case 'i': - dump_int_status(); - return EXIT_SUCCESS; - case 'v': - printf("IC Version: 0x%hx\n", ch375_get_version()); - return EXIT_SUCCESS; - default: - printf("Unknown mode %c\n", mode); - return EXIT_FAILURE; - } -} diff --git a/usb.h b/usb.h deleted file mode 100644 index d8e156e..0000000 --- a/usb.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * CHU - driver for CH375 in disk mode - * Copyright (C) 2024 Javier S. Pedro - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -#ifndef USB_H -#define USB_H - -#include - -typedef enum usb_descriptor_type { - USB_DESCRIPTOR_TYPE_DEVICE = 1, - USB_DESCRIPTOR_TYPE_CONFIG = 2 -} usb_descriptor_type; - -typedef _Packed struct usb_device_descriptor { - uint8_t bLength; - uint8_t bDescriptorType; - uint16_t bcdUSB; - uint8_t bDeviceClass; - uint8_t bDeviceSubClass; - uint8_t bDeviceProtocol; - uint8_t bMaxPacketSize0; - uint16_t idVendor; - uint16_t idProduct; - uint16_t bcdDevice; - uint8_t iManufacturer; - uint8_t iProduct; - uint8_t iSerialNumber; - uint8_t bNumConfigurations; -} usb_device_descriptor; - -#endif // USB_H diff --git a/utils.h b/utils.h deleted file mode 100644 index 138a774..0000000 --- a/utils.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * CHU - driver for CH375 in disk mode - * Copyright (C) 2024 Javier S. Pedro - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -#ifndef UTILS_H -#define UTILS_H - -#include - -static inline __segment get_cs(void); -#pragma aux get_cs = "mov ax, cs" value [ax] modify exact [] nomemory - -static inline __segment get_ds(void); -#pragma aux get_ds = "mov ax, ds" value [ax] modify exact [] nomemory - -static void load_ds_from_cs(void); -#pragma aux load_ds_from_cs = \ - "push cs" \ - "pop ds" \ - __modify __exact [] - -#define SWAPNC_32(w) (\ - (((w) & 0x000000FFUL) << 24) |\ - (((w) & 0x0000FF00UL) << 8) |\ - (((w) & 0x00FF0000UL) >> 8) |\ - (((w) & 0xFF000000UL) >> 24)\ - ) - -static inline uint16_t div(uint32_t dividend, uint16_t divisor); -#pragma aux div = \ - "div bx" \ - __parm [dx ax] [bx] \ - __value [ax] \ - __modify __exact [dx ax] - -static inline uint32_t mul(uint16_t a, uint16_t b); -#pragma aux mul = \ - "mul dx" \ - __parm [ax] [dx] \ - __value [dx ax] \ - __modify __exact [dx ax] - - - -#endif // UTILS_H 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 . + */ + +#ifndef CH375VXD_H +#define CH375VXD_H + +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include + +#include + +#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 +#include +#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 + uint8_t DCB_BDD_BD_Minor_Version; // INIT + 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 + +#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 + +#include + +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 . + */ + +#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 -- cgit v1.2.3