/*
* CHU - driver for CH375 in disk mode
* Copyright (C) 2024 Javier S. Pedro
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include "inc/dosapi.h"
#include "inc/dosint.h"
#include "inc/bda.h"
#include "chudisk.h"
#include "ch375.h"
#include "dlog.h"
#include "cio.h"
#include "utils.h"
static int parse_int(const char far * far *s)
{
int i = 0;
while (**s >= '0' && **s <= '9') {
i = (i * 10) + (**s - '0');
(*s)++;
}
if (**s == ' ' || **s == '\r' || **s == '\n') {
return i;
} else {
return -1;
}
}
static int parse_hex(const char far * far *s)
{
int i = 0;
while ((**s >= '0' && **s <= '9') || (**s >= 'A' && **s <= 'F') || (**s >= 'a' && **s <= 'f')) {
if (**s >= '0' && **s <= '9') i = (i * 16) + (**s - '0');
else if (**s >= 'A' && **s <= 'F') i = (i * 16) + (**s - 'A');
else if (**s >= 'a' && **s <= 'f') i = (i * 16) + (**s - 'a');
(*s)++;
}
if (**s == ' ' || **s == '\r' || **s == '\n') {
return i;
} else {
return -1;
}
}
static bool parse_cmdline(const char far *cmdline)
{
// Source command line string
// E.g. DEVICE.SYS /N3\r\n
const char far *s = cmdline;
// Skip filename part (search for first sep)
while (*s != ' ' && *s != '\r' && *s != '\n') { s++; }
if (*s == '\r' || *s == '\n') return true; // No arguments
// Now skip whitespace
while (*s == ' ') { s++; }
// Now handle options like /N3 /X4
while (*s != '\r' && *s != '\n') {
char c = *s++, opt;
int num;
switch (c) {
case '/':
opt = *s++;
switch (opt) {
case 'N':
case 'n': // number of units
num = parse_int(&s);
if (num > 0 && num <= MAX_UNITS) {
num_units = num;
} else {
c_printf("invalid value '%d' for option '%c'\n", num, opt);
return false;
}
break;
case 'P':
case 'p': // IO port
num = parse_hex(&s);
if (num > 0) {
ch375_port = num;
} else {
c_printf("invalid value '%d' for option '%c'\n", num, opt);
return false;
}
break;
case 'R':
case 'r': // Read only
read_only = true;
break;
case '3': // Show FAT32 partitions
show_fat32 = true;
break;
default:
c_printf("invalid option '%c'\n", opt);
return false;
}
break;
case ' ':
break;
default:
c_printf("invalid syntax '%c'\n", c);
return false;
}
}
dprintf("Configured num drives: %hu\n", num_units);
return true;
}
static void fill_initial_bpb(DOS_BPB *bpb)
{
// Dummy, only used during driver init
// Most likely these values can be anything you want,
// as long as it remotely looks like a valid FAT16 BPB
bpb->bytesPerSector = 512;
bpb->sectorsPerCluster = 1;
bpb->reservedSectors = 0;
bpb->numberOfFATs = 2;
bpb->rootDirEntries = 16;
bpb->numberOfSectors = 512;
bpb->mediaType = DOS_BPB_MEDIA_HDD;
bpb->sectorsPerFAT = 1;
bpb->sectorsPerTrack = 63;
bpb->numberOfHeads = 255;
bpb->hiddenSectors = 0;
bpb->numberOfSectorsLarge = 0;
memcpy(bpb->fat16.volumeLabel, "NO NAME ", 11);
}
void driver_init(DOS_DRS __far *drs)
{
unsigned i;
uint16_t dos_version;
uint8_t dos_major, dos_minor;
uint8_t hw_version;
dlog_init();
// Defaults
ch375_port = 0x260;
num_units = 1;
read_only = false;
show_fat32 = false;
// If DOS > 7.1, enable FAT32 by default
dos_version = dos_get_version();
dos_major = dos_version & 0xFF;
dos_minor = (dos_version >> 8) & 0xFF;
dprintf("DOS version %d.%d\n", dos_major, dos_minor);
if (dos_major > 7 || (dos_major == 7 && dos_minor >= 10)) {
show_fat32 = true;
}
// Parse command line arguments
if (!parse_cmdline(drs->cmd.init.in.cmdline)) {
c_puts("CHUDISK: Syntax error in cmdline arguments");
goto fail;
}
// Try to initialize CH375
dputs("CH375 reset...");
ch375_reset();
bda_wait_ticks(3); // wait 100-150ms
dputs("CH375 reset wait done");
if (ch375_check_exists(0xA5) != (uint8_t)~0xA5) {
c_printf("CHUDISK: CH375 not detected at @%x", ch375_port);
goto fail;
}
hw_version = ch375_get_version();
// Do not really set up the chip until it is used
ch375_set_usb_mode(CH375_USB_MODE_HOST_DISABLED);
ch375_enter_sleep();
data.ready = false;
data.sectors = 0;
data.bytesPerSector = 0;
// Build the template BPBs
drs->cmd.init.out.number_units = num_units;
for (i = 0; i < num_units; i++) {
fill_initial_bpb(&data.unit[i].bpb);
data.bpb_ptrs[i] = &data.unit[i].bpb;
}
drs->cmd.init.out.bpbs = data.bpb_ptrs;
drs->cmd.init.out.break_addr = MK_FP(get_cs(), FP_OFF(&resident_end));
drs->status = DOS_DD_STATUS_DONE;
c_printf("CHUDISK v.0 (HW v.%x), drives", hw_version);
for (i = 0; i < num_units; i++) {
c_printf(" %c:", drive_index_to_letter(drs->cmd.init.in.first_drive_number + i));
}
c_putc('\n');
return;
fail:
drs->status = DOS_DD_STATUS_DONE | DOS_DD_STATUS_ERROR | DOS_DD_ERR_GENERAL_FAILURE;
drs->cmd.init.out.number_units = 0;
drs->cmd.init.out.break_addr = MK_FP(get_cs(), 0); // Deallocate everything
return;
}