diff options
| author | Javier <dev.git@javispedro.com> | 2026-10-26 00:50:06 +0100 |
|---|---|---|
| committer | Javier <dev.git@javispedro.com> | 2026-10-26 00:50:06 +0100 |
| commit | 6b8a368c51082fdd010ef5e1b0be4eb9b5059d1a (patch) | |
| tree | e274d41a8dd5e912e20617a937a1fda5af91f8de /w9x/chupdr.c | |
| parent | 9b1cf601360fb194047e6dcc2af75c2f38c611ec (diff) | |
| download | chu-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.c | 816 |
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; +} |
