/* * 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; }