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