aboutsummaryrefslogtreecommitdiff
path: root/w9x/chupdr.c
diff options
context:
space:
mode:
authorJavier <dev.git@javispedro.com>2026-10-26 00:50:06 +0100
committerJavier <dev.git@javispedro.com>2026-10-26 00:50:06 +0100
commit6b8a368c51082fdd010ef5e1b0be4eb9b5059d1a (patch)
treee274d41a8dd5e912e20617a937a1fda5af91f8de /w9x/chupdr.c
parent9b1cf601360fb194047e6dcc2af75c2f38c611ec (diff)
downloadchu-6b8a368c51082fdd010ef5e1b0be4eb9b5059d1a.tar.gz
chu-6b8a368c51082fdd010ef5e1b0be4eb9b5059d1a.zip
refactor common parts into inc/ch375.h and add win9x, linux drivers
Diffstat (limited to 'w9x/chupdr.c')
-rw-r--r--w9x/chupdr.c816
1 files changed, 816 insertions, 0 deletions
diff --git a/w9x/chupdr.c b/w9x/chupdr.c
new file mode 100644
index 0000000..df6c8bd
--- /dev/null
+++ b/w9x/chupdr.c
@@ -0,0 +1,816 @@
+/*
+ * CHU - This is a IOS port driver for the CH375 in disk mode
+ * Copyright (C) 2026 Javier S. Pedro
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <scsi.h>
+
+#include "inc/vxd.h"
+#include "inc/vmm.h"
+#include "inc/configmg.h"
+#include "inc/ios.h"
+
+#include "ch375vxd.h"
+#include "utils.h"
+#include "dlog.h"
+
+int control(uint32_t message, uint32_t ebx, uint32_t ecx, uint32_t edx, uint32_t esi, uint32_t edi);
+#pragma aux control "*" parm routine [eax] [ebx] [ecx] [edx] [esi] [edi] value [eax] modify []
+
+void aer(AEP *aep);
+#pragma aux (__cdecl) aer
+
+void ior(IOP *iop);
+#pragma aux (__cdecl) ior
+
+/** Note: due to vxdldr limitations, we can't have DATA segments here.
+ * Put all static/global variables in the code segment! */
+
+ILB __based(__segname("_CODE")) driver_ilb = {0};
+
+DRP __based(__segname("_CODE")) driver_drp = {
+ .DRP_eyecatch_str = "CHUDXXXX",
+ .DRP_LGN = DRP_MISC_PD,
+ .DRP_aer = aer,
+ .DRP_ilb = (ILB*) &driver_ilb,
+ .DRP_ascii_name = "CHUDISK Port"
+};
+
+VxD_Desc_Block __based(__segname("_CODE")) vxd_ddb = {
+ .sdk_version = SDK_VERSION_4,
+ .req_device_number = UNDEFINED_DEVICE_ID,
+ .dev_major_version = 1,
+ .dev_minor_version = 0,
+ .name = "CHUDISK ",
+ .init_order = UNDEFINED_INIT_ORDER,
+ .control_proc = control,
+ .reference_data = (void*) &driver_drp,
+ .prev = DDB_PREV_INIT,
+ .size = sizeof(VxD_Desc_Block),
+ .reserved = {0}
+};
+#pragma aux vxd_ddb "*" // must be exported
+
+/** Keeps track of how many devices we are managing. Usually 0 or 1.
+ * If 0, we'll try to unload ourselves. */
+static unsigned short __based(__segname("_CODE")) managed_devices = 0;
+
+/** Our DDB structure, which contains all our private fields. */
+typedef struct CHUDISK_DDB {
+ DDB ddb;
+ struct ch375 dev;
+ enum {
+ /** Initial state -- not doing anything. */
+ STATE_IDLE = 0,
+ STATE_CONNECTED
+ } state;
+ HTIMEOUT timeout;
+ unsigned sector_size;
+} CHUDISK_DDB;
+
+#if ENABLE_DLOG && DLOG_TARGET == DLOG_TARGET_UNDEF
+// Outputs DLOG to the IOS debug log
+/** Initializes the debug log port. */
+static void dlog_init() { }
+
+/** Logs a single character to the debug message IO port. */
+static inline void dputc(char c) {
+ if (driver_ilb.ILB_dprintf) {
+ if (c == '\n') {
+ driver_ilb.ILB_dprintf("\n\r");
+ } else {
+ driver_ilb.ILB_dprintf("%c", &c);
+ }
+ }
+}
+#endif
+
+#if ENABLE_DLOG
+#include "dumpdcb.h"
+#else
+static inline void dcb_dump(DCB *d) { (void)d; }
+#endif
+
+/** Waits for the CH375 to generate an interrupt, returns result value.
+ * @param ms max milliseconds to wait for an interrupt */
+static uint8_t wait_for_ch375_result(struct ch375 *dev, unsigned int ms)
+{
+ uint32_t start_time = Get_Last_Updated_System_Time(), elapsed;
+ while (!ch375_poll_interrupt(dev) &&
+ (elapsed = Get_Last_Updated_System_Time() - start_time) < ms) {
+
+ }
+
+ if (ch375_poll_interrupt(dev)) {
+ uint8_t status = ch375_get_int_status(dev);
+ return status;
+ } else {
+ dprintf("%s: timeout! waited %u ms\n", __func__, elapsed);
+ return 0;
+ }
+}
+
+/** Gets sense/error information and puts it on the debug log. */
+static void disk_sense_error(CHUDISK_DDB *ddb)
+{
+#if ENABLE_DLOG
+ char buffer[CH375_BLOCK_SIZE];
+ unsigned len;
+
+ dprintf("%s\n", __func__);
+
+ ch375_send_command(&ddb->dev, CH375_DISK_R_SENSE);
+ if (wait_for_ch375_result(&ddb->dev, 1000) != CH375_INT_USB_SUCCESS) {
+ dputs("could not sense error");
+ return;
+ }
+
+ ch375_send_command(&ddb->dev, CH375_RD_USB_DATA);
+ len = ch375_read_data_block(&ddb->dev, &buffer[0]);
+
+ dprintf(" sense read data block len = %d\n", len);
+ dprintf(" sense packet %hx %hx %hx %hx %hx %hx %hx %hx\n",
+ buffer[0], buffer[1], buffer[2], buffer[3],
+ buffer[4], buffer[5], buffer[6], buffer[7]);
+ dprintf(" %hx %hx %hx %hx %hx %hx %hx %hx\n",
+ buffer[8], buffer[9], buffer[10], buffer[11],
+ buffer[12], buffer[13], buffer[14], buffer[15]);
+#else
+ (void)ddb;
+#endif
+}
+
+/** Read N sectors from the disk
+ * @return number of sectors read
+ */
+static unsigned disk_read_sectors(CHUDISK_DDB *ddb, uint8_t *buffer, uint32_t lba, uint8_t num)
+{
+ uint8_t result;
+ unsigned total = 0; // Total bytes written
+
+ dprintf("disk read buf=%p sector=%lu num=%hu\n", buffer, lba, num);
+
+ // Initiate read operation
+ ch375_disk_read(&ddb->dev, lba, num);
+
+ // Now keep going as long as ch375 says to keep going
+ while ((result = wait_for_ch375_result(&ddb->dev, 1000)) == CH375_INT_USB_DISK_READ) {
+ unsigned len;
+
+ // Sanity check
+ if (total / ddb->sector_size >= num) {
+ dputs("read overflow");
+ break;
+ }
+
+ ch375_send_command(&ddb->dev, CH375_RD_USB_DATA);
+ len = ch375_read_data_block(&ddb->dev, &buffer[total]);
+
+ //dprintf(" disk read data block len = %d\n", len);
+
+ if (len == 0) {
+ // Done?
+ break;
+ }
+
+ total += len;
+
+ // Continue
+ ch375_send_command(&ddb->dev, CH375_DISK_RD_GO);
+ }
+
+ dprintf(" disk read total bytes = %u, sectors = %u, final status = 0x%hx\n",
+ total, total / ddb->sector_size, result);
+
+ if (result == CH375_INT_USB_DISK_ERR) {
+ disk_sense_error(ddb);
+ }
+
+ return total / ddb->sector_size;
+}
+
+/** Write N sectors to the disk
+ * @return number of sectors written
+ */
+static unsigned disk_write_sectors(CHUDISK_DDB *ddb, uint8_t *buffer, uint32_t lba, uint8_t num)
+{
+ uint8_t result;
+ unsigned total = 0; // Total bytes written
+
+ dprintf("disk write buf=%p sector=%lu num=%hu\n", buffer, lba, num);
+
+ // Initiate operation
+ ch375_disk_write(&ddb->dev, lba, num);
+
+ // Now keep going as long as ch375 says to keep going
+ while ((result = wait_for_ch375_result(&ddb->dev, 1000)) == CH375_INT_USB_DISK_WRITE) {
+ const unsigned len = CH375_BLOCK_SIZE;
+
+ // Sanity check
+ if (total / ddb->sector_size >= num) {
+ dputs("write overflow");
+ break;
+ }
+
+ ch375_send_command(&ddb->dev, CH375_WR_USB_DATA7);
+ ch375_write_data_block(&ddb->dev, &buffer[total], len);
+
+ total += len;
+
+ // Continue
+ ch375_send_command(&ddb->dev, CH375_DISK_WR_GO);
+ }
+
+ dprintf(" disk write total bytes = %u, sectors = %u, final status = 0x%hx\n",
+ total, total / ddb->sector_size, result);
+
+ if (result == CH375_INT_USB_DISK_ERR) {
+ disk_sense_error(ddb);
+ }
+
+ return total / ddb->sector_size;
+}
+
+/** Reinquiries disk size and recomputes the geometry from it.
+ @return true if there was a disk and we could refresh geometry. */
+static bool disk_refresh_geometry(CHUDISK_DDB *ddb, DCB *dcb)
+{
+ char buffer[CH375_BLOCK_SIZE];
+
+ if (ddb->state == STATE_IDLE) {
+ // No disk. Clear all geometry flags.
+ dcb->DCB_cmn.DCB_TSD_Flags &= ~(DCB_TSD_APPARENT_PRE_SET | DCB_TSD_ACTUAL_PRE_SET);
+ return false;
+ }
+
+ dprintf("%s: reading disk size\n", __func__);
+
+ if (ddb->state != STATE_CONNECTED) {
+ dprintf("%s: invalid state\n", __func__);
+ return false;
+ }
+
+ // Ask controller for disk size
+ ch375_send_command(&ddb->dev, CH375_DISK_SIZE);
+ if (wait_for_ch375_result(&ddb->dev, 200) != CH375_INT_USB_SUCCESS) {
+ return false;
+ }
+
+ ch375_send_command(&ddb->dev, CH375_RD_USB_DATA);
+ if (ch375_read_data_block(&ddb->dev, buffer) < 8) {
+ dputs("disk size command return less than 8 bytes");
+ return false;
+ }
+
+ dcb->DCB_actual_blk_size = SWAPNC_32(*(uint32_t*)&buffer[4]);
+ dcb->DCB_actual_sector_cnt = SWAPNC_32(*(uint32_t*)&buffer[0]);
+
+ dprintf("%s: sectors %lu bytesPerSector %lu\n", __func__, (uint32_t)dcb->DCB_actual_sector_cnt, dcb->DCB_actual_blk_size);
+
+ // Ok, CHS time
+ guess_geometry( dcb->DCB_actual_sector_cnt, &dcb->DCB_actual_cyl_cnt, &dcb->DCB_actual_head_cnt, &dcb->DCB_actual_spt);
+ dcb->DCB_cmn.DCB_TSD_Flags |= DCB_TSD_ACTUAL_PRE_SET;
+
+ // Initilize apparent geometry from actual geometry, even though I'm not sure it is required
+ dcb->DCB_bdd.DCB_apparent_blk_size = dcb->DCB_actual_blk_size;
+ dcb->DCB_cmn.DCB_apparent_blk_shift = ulog2(dcb->DCB_bdd.DCB_apparent_blk_size);
+ dcb->DCB_bdd.DCB_apparent_sector_cnt = dcb->DCB_actual_sector_cnt;
+ dcb->DCB_bdd.DCB_apparent_cyl_cnt = dcb->DCB_actual_cyl_cnt;
+ dcb->DCB_bdd.DCB_apparent_head_cnt = dcb->DCB_actual_head_cnt;
+ dcb->DCB_bdd.DCB_apparent_spt = dcb->DCB_actual_spt;
+ dcb->DCB_cmn.DCB_TSD_Flags |= DCB_TSD_APPARENT_PRE_SET;
+
+ // max 255 blocks at a time
+ dcb->DCB_max_xfer_len = 255 * dcb->DCB_actual_blk_size;
+
+ if (ddb->sector_size != dcb->DCB_actual_blk_size) {
+ ddb->sector_size = dcb->DCB_actual_blk_size;
+ // Configure device for the new sector size
+ ch375_write_reg(&ddb->dev, CH375_REG_DISK_PKT_P_SEC, ddb->sector_size / CH375_BLOCK_SIZE);
+ }
+
+ return true;
+}
+
+/** Enables CH375 USB host mode, initializes "disk" mode on it
+ * and connects to a disk. */
+static bool disk_connect(CHUDISK_DDB *ddb)
+{
+ int tries;
+
+ dputs("switch to usb host");
+ ch375_set_usb_mode(&ddb->dev, CH375_USB_MODE_HOST_AUTO_SOF);
+ if (wait_for_ch375_result(&ddb->dev, 100) != CH375_INT_USB_CONNECT) {
+ dputs("could not turn on usb host");
+ return false;
+ }
+
+ dputs("try init disk");
+ ch375_disk_init(&ddb->dev);
+ if (wait_for_ch375_result(&ddb->dev, 1000) != CH375_INT_USB_SUCCESS) {
+ dputs("could not init disk");
+ return false;
+ }
+
+ // looks like we have to keep polling by asking "is the disk ready?"
+ // even if each time we ask we are already polling
+ tries = 3;
+ while (tries-- > 0) {
+ ch375_test_disk_ready(&ddb->dev);
+ if (wait_for_ch375_result(&ddb->dev, 1000) == CH375_INT_USB_SUCCESS) {
+ dputs("disk now init");
+ return true;
+ }
+ // give ample time (100ms) for drive to settle between tries
+ driver_ilb.ILB_Wait_10th_Sec();
+ }
+
+ dputs("disk failed to init");
+ return false;
+}
+
+/** Check if media has changed since last time.
+ * In addition, it will also try to connect to the disk.
+ * @return true if media changed. */
+static bool disk_update_media_state(CHUDISK_DDB *ddb, DCB *dcb)
+{
+ bool changed = false;
+ switch (ddb->state) {
+ case STATE_IDLE:
+ dprintf("%s: state idle -> changed=false\n", __func__);
+ break;
+ case STATE_CONNECTED:
+ // Recheck if disk is still ready
+ ch375_test_disk_ready(&ddb->dev);
+ if (wait_for_ch375_result(&ddb->dev, 100) == CH375_INT_USB_SUCCESS) {
+ // CH375 is ready
+ dprintf("%s: state connected -> disk ready -> changed=false\n", __func__);
+ } else {
+ // CH375 is not ready but we thought it was. USB has been removed!
+ dprintf("%s: state connected -> disk NOT ready -> changed=true\n", __func__);
+ changed = true;
+ ddb->state = STATE_IDLE;
+ }
+ break;
+ default:
+ dprintf("%s: busy state %u\n", __func__, ddb->state);
+ return false;
+ }
+ if (ddb->state == STATE_IDLE) {
+ // Geometry becomes invalid
+ dcb->DCB_cmn.DCB_TSD_Flags &= ~(DCB_TSD_APPARENT_PRE_SET | DCB_TSD_ACTUAL_PRE_SET);
+ // Try to connect to the disk
+ dprintf("%s: trying to connect\n", __func__);
+ if (disk_connect(ddb)) {
+ dprintf("%s: connected\n", __func__);
+ ddb->state = STATE_CONNECTED;
+ changed = true;
+ }
+ }
+ if (changed) {
+ dcb->DCB_cmn.DCB_device_flags |= DCB_DEV_UNCERTAIN_MEDIA;
+ if (ddb->state == STATE_IDLE) {
+ // if we can't find new media... for sure it has changed.
+ dcb->DCB_cmn.DCB_device_flags |= DCB_DEV_MEDIA_CHANGED;
+ }
+ } else {
+ dcb->DCB_cmn.DCB_device_flags &= ~(DCB_DEV_MEDIA_CHANGED | DCB_DEV_UNCERTAIN_MEDIA);
+ }
+ return changed;
+}
+
+/** IO request to recompute geometry.
+ * Normally called after a media change. */
+static bool ior_compute_geom(CHUDISK_DDB *ddb, IOP *iop)
+{
+ DCB *dcb = iop->IOP_physical_dcb;
+
+ dprintf("%s\n", __func__);
+
+ dcb_dump(dcb);
+
+ if (disk_update_media_state(ddb, dcb)) {
+ // report media change
+ dprintf("%s: uncertain media\n", __func__);
+ iop->IOP_ior.IOR_status = IORS_UNCERTAIN_MEDIA;
+ return true;
+ }
+ if (ddb->state == STATE_IDLE) {
+ dprintf("%s: no media\n", __func__);
+ iop->IOP_ior.IOR_status = IORS_NO_MEDIA;
+ return true;
+ }
+
+ if (disk_refresh_geometry(ddb, dcb)) {
+ dprintf("%s: success\n", __func__);
+ iop->IOP_ior.IOR_status = IORS_SUCCESS;
+ } else {
+ dprintf("%s: media error\n", __func__);
+ iop->IOP_ior.IOR_status = IORS_MEDIA_ERROR;
+ }
+
+ return true;
+}
+
+/** Checks if media has been changed since last call.
+ * Should return either UNCERTAIN_MEDIA or SUCCESS */
+static bool ior_media_check_reset(CHUDISK_DDB *ddb, IOP *iop)
+{
+ DCB *dcb = iop->IOP_physical_dcb;
+
+ dprintf("%s\n", __func__);
+ dcb_dump(dcb);
+
+ if (disk_update_media_state(ddb, dcb)) {
+ // report media change
+ dprintf("%s: uncertain media\n", __func__);
+ iop->IOP_ior.IOR_status = IORS_UNCERTAIN_MEDIA;
+ return true;
+ }
+
+ dprintf("%s: success\n", __func__);
+ iop->IOP_ior.IOR_status = IORS_SUCCESS;
+ return true;
+}
+
+/** Helper function to do one transfer (e.g. one SG_Buffer). */
+static uint32_t ior_xfer_do(CHUDISK_DDB *ddb, IOR *ior, uint8_t *buffer, uint32_t sector, uint32_t nsectors)
+{
+ if (ior->IOR_func == IOR_WRITE) {
+ return disk_write_sectors(ddb, buffer, sector, nsectors);
+ } else {
+ return disk_read_sectors(ddb, buffer, sector, nsectors);
+ }
+}
+
+/* Main entry point for I/O operation (read, write). */
+static bool ior_xfer(CHUDISK_DDB *ddb, IOP *iop)
+{
+ DCB *dcb = iop->IOP_physical_dcb;
+ IOR *ior = &iop->IOP_ior;
+ uint32_t sector = ior->IOR_start_addr;
+
+ dprintf("%s: %s start %u flags 0x%x\n", __func__,
+ ior->IOR_func == IOR_WRITE ? "write" : "read",
+ sector, ior->IOR_flags);
+ dcb_dump(dcb);
+
+ if (ior->IOR_flags & IORF_CHAR_COMMAND) {
+ // We do not support character operations. Only blocks.
+ ior->IOR_status = IORS_NOT_READY;
+ return true;
+ }
+
+ if (ior->IOR_flags & IORF_SCATTER_GATHER) {
+ _BlockDev_Scatter_Gather* sgd;
+ uint32_t nsectors;
+ for (sgd = ior->IOR_buffer_ptr; (nsectors = sgd->BD_SG_Count); sgd++) {
+ uint8_t *buffer = sgd->BD_SG_Buffer_Ptr;
+ dprintf("%s: sgd buffer %p sector %lu nsectors %lu\n", __func__, buffer, sector, nsectors);
+ if (ior_xfer_do(ddb, ior, buffer, sector, nsectors) != nsectors) {
+ ior->IOR_status = IORS_HW_FAILURE;
+ return true;
+ }
+ sector += nsectors;
+ }
+ } else {
+ uint8_t *buffer = ior->IOR_buffer_ptr;
+ uint32_t nsectors = ior->IOR_xfer_count;
+ dprintf("%s: plain buffer %p sector %lu nsectors %lu\n", __func__, buffer, sector, nsectors);
+ if (ior_xfer_do(ddb, ior, buffer, sector, nsectors) != nsectors) {
+ ior->IOR_status = IORS_HW_FAILURE;
+ return true;
+ }
+ }
+
+ ior->IOR_status = IORS_SUCCESS;
+ return true;
+}
+
+/* The I/O routine. This handles I/O request packets (IOPs), like READ or WRITE. */
+void ior(IOP *iop)
+{
+ CHUDISK_DDB *ddb = (CHUDISK_DDB*) iop->IOP_calldown_ptr->DCB_cd_ddb;
+ DCB *dcb = iop->IOP_physical_dcb;
+ bool handled = false;
+ dprintf("%s: ior_func=0x%x iop=%p ddb=%p flags=%x\n", __func__, iop->IOP_ior.IOR_func, iop, ddb, iop->IOP_ior.IOR_flags);
+
+ dcb->DCB_cmn.DCB_device_flags |= DCB_DEV_IO_ACTIVE;
+ switch (iop->IOP_ior.IOR_func) {
+ case IOR_COMPUTE_GEOM:
+ handled = ior_compute_geom(ddb, iop);
+ break;
+ case IOR_MEDIA_CHECK:
+ case IOR_MEDIA_CHECK_RESET:
+ handled = ior_media_check_reset(ddb, iop);
+ break;
+ case IOR_READ:
+ case IOR_WRITE:
+ handled = ior_xfer(ddb, iop);
+ break;
+ case IOR_FLUSH_DRIVE:
+ case IOR_FLUSH_DRIVE_AND_DISCARD:
+ handled = true; // Nothing to flush
+ break;
+ default:
+ dprintf("%s: unknown func=0x%x\n", __func__, iop->IOP_ior.IOR_func);
+ // Let everyone else have a go at this request
+ handled = false;
+ break;
+ }
+ dcb->DCB_cmn.DCB_device_flags &= ~DCB_DEV_IO_ACTIVE;
+ dprintf("%s: iop=%p %s status=%u\n", __func__, iop, handled ? "HANDLED" : "NOThandled", iop->IOP_ior.IOR_status);
+ if (handled) {
+ IopCallback(iop);
+ } else {
+ IopCalldown(iop);
+ }
+}
+
+/** The initialization routine.
+ * It should find out all information about the adapter we are supposed to be driving
+ * from CONFIGMG and the PnP device node, initialize the adapter in question,
+ * and write everything that we need in a new DDB which will be passed to us
+ * in future calls. */
+static AEP_result aer_initialize(AEP_bi_init *aep)
+{
+ DEVNODE devNode = (DEVNODE) aep->AEP_bi_i_hdevnode;
+ CMCONFIG config;
+ CONFIGRET cr;
+ CHUDISK_DDB *ddb;
+ struct ch375 dev;
+
+ dprintf("%s: devnode %p regkey %p\n", __func__, devNode, aep->AEP_bi_i_regkey);
+ // TODO Wish I could print the path of the regkey here.
+
+ // Get whichever configuration (IO ports, IRQ, etc.) has been allocated to our device
+ cr = CONFIGMG_Get_Alloc_Log_Conf(&config, devNode, CM_GET_ALLOC_LOG_CONF_ALLOC);
+ if (cr != CR_SUCCESS) {
+ dprintf("%s: get alloc log conf failure %u\n", __func__, cr);
+ return AEP_FAILURE;
+ }
+
+ dprintf("%s: config nports %u port len %u port base 0x%x\n", __func__,
+ config.num_io_ports, config.io_port_length[0], config.io_port_base[0]);
+
+ if (config.num_io_ports != 1 || config.io_port_length[0] != 2) {
+ // incorrect configuration
+ return AEP_FAILURE;
+ }
+
+ // Let's check if hardware exists at this address
+ dev.io_base = config.io_port_base[0];
+ dprintf("%s: io_base=%x reset...\n", __func__, dev.io_base);
+ ch375_reset(&dev);
+ driver_ilb.ILB_Wait_10th_Sec(); // Wait 100ms
+
+ if (ch375_check_exists(&dev, 0xA5) != (uint8_t)~0xA5) {
+ dprintf("%s: CH375 not detected at @%x\n", __func__, dev.io_base);
+ return AEP_FAILURE;
+ }
+
+ // Store it into the DDB, this also tells IOS we found one adapter.
+ ddb = (CHUDISK_DDB*) IspCreateDdb(&driver_ilb, sizeof(CHUDISK_DDB));
+ dprintf("%s: created 1 ddb at %p\n", __func__, ddb);
+ if (!ddb) {
+ return AEP_FAILURE;
+ }
+
+ // Fill in fields of our DDB
+ ddb->ddb.DDB_number_buses = 1;
+ ddb->dev.io_base = dev.io_base;
+ ddb->state = STATE_IDLE;
+ ddb->timeout = 0;
+ ddb->sector_size = 512;
+
+ // IOS will call device inquiry for each DDB created here.
+
+ // No SCSI...
+ aep->AEP_bi_i_max_lun = 0;
+ aep->AEP_bi_i_max_target = 0;
+
+ return AEP_SUCCESS;
+}
+
+/** Boot complete.
+ * Return failure if we're managing no devices and want to be unloaded. */
+static AEP_result aer_boot_complete(AEP_boot_done *aep)
+{
+ (void)aep;
+ dlog_init();
+ dprintf("%s: managed_devices=%u\n", __func__, managed_devices);
+ return managed_devices > 0 ? AEP_SUCCESS : AEP_FAILURE;
+}
+
+/** Called to configure a specific unit of an adapter.
+ * Driver must fill in DCB fields and insert itself into callback chain. */
+static AEP_result aer_config_dcb(AEP_dcb_config *aep)
+{
+ DCB * dcb = (DCB*) aep->AEP_d_c_dcb;
+ dprintf("%s: ddb %p dcb %p flags=%x\n", __func__, aep->AEP_d_c_hdr.AEP_ddb, dcb, dcb->DCB_cmn.DCB_device_flags);
+
+ // We get DCBs for both physical and logical devices, but we only want to handle physical
+ if (dcb->DCB_cmn.DCB_device_flags & DCB_DEV_PHYSICAL) {
+ CHUDISK_DDB *ddb = (CHUDISK_DDB*) aep->AEP_d_c_hdr.AEP_ddb;
+
+ if (managed_devices >= 1) {
+ // Only support 1 adapter, 1 unit
+ return AEP_FAILURE;
+ }
+
+ // Fill in DCB fields now
+ dcb->DCB_actual_blk_size = 512; // will update later if necessary
+ dcb->DCB_actual_blk_size = 9; // ulog2(512)
+ dcb->DCB_max_xfer_len = 255 * 512; // 255 packets
+ dcb->DCB_max_sg_elements = 255;
+
+ dcb_dump(dcb);
+
+ // Initialize unit (if necessary)
+ disk_update_media_state(ddb, dcb);
+ disk_refresh_geometry(ddb, dcb);
+
+ // Insert ourselves into the callback chain
+ dprintf("%s: inserting our calldown %p at dcb %p lgn %u\n",
+ __func__, ior, dcb, aep->AEP_d_c_hdr.AEP_lgn);
+ if (IspInsertCalldown(&driver_ilb, dcb, ior, (DDB*) ddb, 0,
+ DCB_dmd_serialize /* one request at a time */
+ | DCB_dmd_clear_media_chg | DCB_dmd_no_xclusive,
+ aep->AEP_d_c_hdr.AEP_lgn) != 0) {
+ dprintf("%s: isp insert calldown failure\n", __func__);
+ return AEP_FAILURE;
+ }
+
+ managed_devices++;
+ }
+
+ return AEP_SUCCESS;
+}
+
+/** Called to UNconfigure a specific adapter. */
+static AEP_result aer_unconfig_dcb(AEP_dcb_unconfig *aep)
+{
+ DCB * dcb = (DCB*) aep->AEP_d_u_dcb;
+ dprintf("%s: ddb %p dcb %p flags=%x\n", __func__, aep->AEP_d_u_hdr.AEP_ddb, dcb, dcb->DCB_cmn.DCB_device_flags);
+
+ if ((dcb->DCB_cmn.DCB_device_flags & DCB_DEV_PHYSICAL)
+ && memcmp(dcb->DCB_port_name, vxd_ddb.name, sizeof(dcb->DCB_port_name)) == 0) {
+ dprintf("%s: got our phys dcb\n", __func__);
+
+ managed_devices--;
+ }
+
+ return AEP_SUCCESS;
+}
+
+/** Called to warn us that a DCB is about to be unconfigured,
+ * and we should block until we finish all I/O to it. */
+static AEP_result aer_pend_unconfig_dcb(AEP_dcb_unconfig_pend *aep)
+{
+ DCB * dcb = (DCB*) aep->AEP_d_u_p_dcb;
+ dprintf("%s: ddb %p dcb %p flags=%x\n", __func__, aep->AEP_d_u_p_hdr.AEP_ddb, dcb, dcb->DCB_cmn.DCB_device_flags);
+
+ if ((dcb->DCB_cmn.DCB_device_flags & DCB_DEV_PHYSICAL)
+ && memcmp(dcb->DCB_port_name, vxd_ddb.name, sizeof(dcb->DCB_port_name)) == 0) {
+ dprintf("%s: got our phys dcb\n", __func__);
+ dcb_dump(dcb);
+
+ // We do not really have to do anything to finish all I/O
+ }
+
+ return AEP_SUCCESS;
+}
+
+
+/** Called multiple times, once for each potential unit supported by each adapter.
+ * Must fill in DCB if the unit exists. */
+static AEP_result aer_device_inquiry(AEP_inquiry_device *aep)
+{
+ PDCB dcb = (PDCB) aep->AEP_i_d_dcb;
+ dprintf("%s: ddb %p dcb %p unit %u\n", __func__, aep->AEP_i_d_hdr.AEP_ddb, dcb, dcb->DCB_unit_on_ctl);
+ if (dcb->DCB_unit_on_ctl > 0) {
+ return AEP_NO_MORE_DEVICES;
+ }
+
+ // No need to fill in all DCB fields, just cosmetic ones
+ dcb->DCB_bdd.DCB_BDD_flags |= BDF_Writeable | BDF_Removable | BDF_Serial_Cmd;
+ dcb->DCB_cmn.DCB_device_flags |= DCB_DEV_REMOVABLE | DCB_DEV_WRITEABLE | DCB_DEV_SERIAL_CMD | DCB_DEV_SYNC_MED_CHG_SUPPORT;
+
+ dcb->DCB_cmn.DCB_device_type = DCB_type_disk;
+ dcb->DCB_bus_type = DCB_BUS_ESDI;
+
+ memcpy(dcb->DCB_vendor_id, "CH375 ", sizeof(dcb->DCB_vendor_id));
+ memcpy(dcb->DCB_product_id, "USB Disk ", sizeof(dcb->DCB_product_id));
+ memcpy(dcb->DCB_rev_level, "0001", sizeof(dcb->DCB_rev_level));
+
+ // This we use to identify our DCB later on
+ memcpy(dcb->DCB_port_name, vxd_ddb.name, sizeof(dcb->DCB_port_name));
+
+ return AEP_SUCCESS;
+}
+
+/** Called before removing our driver from memory. */
+static AEP_result aer_uninitialize(AEP_bi_uninit *aep)
+{
+ (void) aep;
+ dputs(__func__);
+ return AEP_SUCCESS;
+}
+
+/** A inflight IO request has timed out. Cancel it and reset device. */
+static AEP_result aer_timeout(AEP_iop_timeout_occurred *aep)
+{
+ IOP* iop = aep->AEP_i_t_o_iop;
+ dprintf("%s: timeout occured for IOP %p\n", __func__, iop);
+ // We have our own timeouts in each poll function
+ // So we can ignore the timeout here.
+ // Re-arm the timer.
+ iop->IOP_timer = iop->IOP_timer_orig;
+ // If we ever move to interrupts, we will have to do something here.
+ return AEP_SUCCESS;
+}
+
+/** The "Asynchronous Event Routine" (i.e. IOS main entry point.) */
+void aer(AEP *aep)
+{
+ switch (aep->AEP_func) {
+ case AEP_INITIALIZE:
+ aep->AEP_result = aer_initialize(&aep->bi_i);
+ break;
+ case AEP_BOOT_COMPLETE:
+ aep->AEP_result = aer_boot_complete(&aep->b_d);
+ break;
+ case AEP_CONFIG_DCB:
+ aep->AEP_result = aer_config_dcb(&aep->d_c);
+ break;
+ case AEP_UNCONFIG_DCB:
+ aep->AEP_result = aer_unconfig_dcb(&aep->d_u);
+ break;
+ case AEP_DEVICE_INQUIRY:
+ aep->AEP_result = aer_device_inquiry(&aep->i_d);
+ break;
+ case AEP_UNINITIALIZE:
+ aep->AEP_result = aer_uninitialize(&aep->bi_u);
+ break;
+ case AEP_IOP_TIMEOUT:
+ aep->AEP_result = aer_timeout(&aep->i_t_o);
+ break;
+ case AEP_PEND_UNCONFIG_DCB:
+ aep->AEP_result = aer_pend_unconfig_dcb(&aep->d_u_p);
+ break;
+ default:
+ dprintf("%s unknown func=%u\n", __func__, aep->AEP_func);
+ aep->AEP_result = AEP_FAILURE;
+ break;
+ }
+}
+
+/** The VxD main entry point. */
+int control(uint32_t message, uint32_t ebx, uint32_t ecx, uint32_t edx, uint32_t esi, uint32_t edi)
+{
+ int ret = 0;
+ (void) ebx;
+ (void) ecx;
+ (void) edx;
+ (void) esi;
+ (void) edi;
+
+ switch (message) {
+ case SYS_DYNAMIC_DEVICE_INIT:
+ dlog_init();
+ ret = IOS_Get_Version();
+ dprintf("ios get version = %x\n", ret);
+ IOS_Register(&driver_drp);
+ dprintf("ios registration result=%u\n", driver_drp.DRP_reg_result);
+ switch (driver_drp.DRP_reg_result) {
+ case DRP_REMAIN_RESIDENT:
+ case DRP_MINIMIZE:
+ break;
+ default: // For anything else, signal an error so that we are unloaded
+ set_carry();
+ return 0;
+
+ }
+ break;
+ }
+
+ clear_carry();
+ return 0;
+}