/* * 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 #include #include #include #include #include "inc/mbr.h" #include "inc/dosdrv.h" #include "chudisk.h" uint16_t ch375_port; bool read_only; bool show_fat32; uint8_t drive; uint32_t partition_start; uint32_t partition_len; #define MAX_PARTITIONS 32 struct partinfo { uint32_t start_sector; uint32_t num_sectors; uint8_t type; } partinfo[MAX_PARTITIONS]; unsigned int num_parts; static int ioctl_read_control(uint8_t drive, void *buffer, int len) { union REGS regs; regs.h.ah = DOS_IOCTL; regs.h.al = 0x04; // Read control string from block device regs.x.bx = drive; regs.x.cx = len; regs.x.dx = FP_OFF(buffer); intdos(®s, ®s); if (regs.x.cflag) { return -1; } else { return regs.x.ax; } } static int ioctl_write_control(uint8_t drive, void *buffer, int len) { union REGS regs; regs.h.ah = DOS_IOCTL; regs.h.al = 0x05; // Write control string to block device regs.x.bx = drive; regs.x.cx = len; regs.x.dx = FP_OFF(buffer); intdos(®s, ®s); if (regs.x.cflag) { return -1; } else { return regs.x.ax; } } static int ioctl_read_abs_sector(uint8_t drive, void *buffer, uint32_t sector, int sectors) { CHUDISK_IOCTL_DISK_DATA ciod; union REGS regs; ciod.rwabs.buffer = buffer; ciod.rwabs.sector = sector; ciod.rwabs.sectors = sectors; regs.h.ah = DOS_IOCTL; regs.h.al = 0x0D; // IOCTL for block devices regs.x.bx = drive; regs.h.ch = DOS_IOCTL_DISK; regs.h.cl = CHUDISK_IOCTL_DISK_READ_ABS_SECTOR; regs.x.dx = FP_OFF(&ciod); intdos(®s, ®s); return regs.x.cflag ? -1 : 0; } static void dos_disk_reset() { union REGS regs; regs.h.ah = 0x0D; // DOS DISK RESET intdos(®s, ®s); } static int parse_chudisk_control_string(const char *b, int len) { // Not sure why I'm doing it this way instead of just sharing a pointer. int i = 0; #if 0 printf("control string:\n"); for (i = 0; i < len; i+=8) { printf(" %hx %hx %hx %hx %hx %hx %hx %hx\n", b[i+0], b[i+1], b[i+2], b[i+3], b[i+4], b[i+5], b[i+6], b[i+7]); } i = 0; #endif while (i < len) { char c = b[i++]; switch (c) { case 'B': memcpy(&ch375_port, &b[i], sizeof(ch375_port)); i+=sizeof(ch375_port); break; case '3': show_fat32 = b[i++]; break; case 'R': read_only = b[i++]; break; case 'S': memcpy(&partition_start, &b[i], sizeof(partition_start)); i+=sizeof(partition_start); break; case 'L': memcpy(&partition_len, &b[i], sizeof(partition_len)); i+=sizeof(partition_len); break; case '\0': return 0; default: fprintf(stderr, "Unidentified control string parameter: '%c'\n", c); return -1; } } return 0; } static bool is_selectable_partition(const MBR_PARTENTRY *partentry) { return partentry->type == MBR_FAT12 || partentry->type == MBR_FAT16_32MB || partentry->type == MBR_FAT16 || partentry->type == MBR_FAT16_LBA || (show_fat32 && (partentry->type == MBR_FAT32 || partentry->type == MBR_FAT32_LBA)); } static const char *get_partition_type(const MBR_PARTENTRY *partentry) { static char buffer[8]; switch (partentry->type) { case MBR_EMPTY: return "invalid"; case MBR_FAT12: return "fat12"; case MBR_FAT16_32MB: case MBR_FAT16: case MBR_FAT16_LBA: return "fat16"; case MBR_IFS: return "ntfs"; case MBR_FAT32: case MBR_FAT32_LBA: return "fat32"; case MBR_EXT: case MBR_EXT_LBA: return "extended"; case MBR_LINUX: case MBR_LINUX_SWAP: return "linux"; default: sprintf(buffer, "%#X", partentry->type); return buffer; } } static const char * show_partition_size(const MBR_PARTENTRY *partentry) { static char buffer[12]; uint32_t size = partentry->lbaLen / 2; STATIC_ASSERT(SECTOR_SIZE == 512); if (size <= 10000) { sprintf(buffer, "%lu KB", size); return buffer; } size /= 1024; if (size <= 10000) { sprintf(buffer, "%lu MB", size); return buffer; } size /= 1024; sprintf(buffer, "%lu GB", size); return buffer; } static const char *get_partition_label(const MBR_PARTENTRY *partentry, const uint32_t start_sector) { static char buffer[SECTOR_SIZE]; DOS_BPB *bpb = (DOS_BPB*) &buffer[DOS_BPB_OFFSET_ON_DISK]; switch (partentry->type) { // Focus on well-known types for now case MBR_FAT12: case MBR_FAT16_32MB: case MBR_FAT16: case MBR_FAT16_LBA: case MBR_FAT32: case MBR_FAT32_LBA: break; default: return " "; } if (ioctl_read_abs_sector(drive, buffer, start_sector, 1) != 0) { fprintf(stderr, "while reading partition boot record at %lu: %s\n", start_sector, strerror(errno)); return " ???? "; } if (bpb->sectorsPerFAT != 0) { // Assume FAT16 return bpb->fat16.volumeLabel; } else { // Assume FAT32 return bpb->fat32.volumeLabel; } } static void print_partition(const uint32_t mbr_sector, const MBR_PARTENTRY *partentry, bool selectable) { const bool ebr = mbr_sector != 0; const uint32_t start_sector = mbr_sector + partentry->lbaFirst; const bool is_current = partition_start == start_sector; const char *typeLabel = get_partition_type(partentry); const char *volumeLabel = get_partition_label(partentry, start_sector); const char *sizeLabel = show_partition_size(partentry); if (partentry->type == MBR_EMPTY) { // Skip empty partitions no matter what return; } if (ebr && (partentry->type == MBR_EXT || partentry->type == MBR_EXT_LBA)) { // Skip displaying EXT partitions EBR, noisy return; } if (selectable) { const unsigned index = num_parts + 1; printf(" %c[%2u] %8s %-11.11s %10s \n", is_current ? '*' : ' ', index, typeLabel, volumeLabel, sizeLabel); } else { printf(" %c[ ] %8s %-11.11s %10s \n", is_current ? '*' : ' ', typeLabel, volumeLabel, sizeLabel); } } static int scan_disk_partitions() { static char buffer[SECTOR_SIZE]; uint32_t next_sector = 0; printf(" Index %8s %-11.11s %10s \n", "Type", "Label", "Size"); // Start with MBR at sector 0 next_sector = 0; do { const uint32_t this_sector = next_sector; const MBR_PARTENTRY *partentry; int i; // Try to read sector if (ioctl_read_abs_sector(drive, buffer, this_sector, 1) != 0) { fprintf(stderr, "while reading %s at disk sector %lu: %s\n", this_sector == 0 ? "MBR" : "EBR", this_sector, strerror(errno)); // Fatal for MBR, non-fatal for EBR if (this_sector == 0) { fprintf(stderr, "is the disk present?\n"); return -1; } break; } // Check for signature if (*(uint16_t*)&buffer[MBR_SIGNATURE_OFFSET_ON_DISK] != 0xAA55U) { fprintf(stderr, "on disk sector %lu: invalid MBR signature %x\n", this_sector, *(uint16_t*)&buffer[MBR_SIGNATURE_OFFSET_ON_DISK]); if (this_sector == 0) return -1; break; } // Parse the MBR partentry = (MBR_PARTENTRY*)&buffer[MBR_PARTENTRY_OFFSET_ON_DISK]; next_sector = 0; // If there is another EBR we will store it here for (i = 0; i < 4 && num_parts < MAX_PARTITIONS; i++) { bool is_selectable = is_selectable_partition(&partentry[i]); print_partition(this_sector, &partentry[i], is_selectable); if (is_selectable) { partinfo[num_parts].type = partentry[i].type; partinfo[num_parts].start_sector = this_sector + partentry[i].lbaFirst; partinfo[num_parts].num_sectors = partentry[i].lbaLen; num_parts++; } if (partentry[i].type == MBR_EXT || partentry[i].type == MBR_EXT_LBA) { // Go to the EBR next next_sector = this_sector + partentry[i].lbaFirst; } } } while (next_sector != 0 && num_parts < MAX_PARTITIONS); return 0; } static void usage() { printf("Usage: chud X:, where X is a drive letter handled by CHUDISK.SYS\n"); } int main(int argc, const char *argv[]) { static char buffer[16]; int i; unsigned part; for (i = 1; i < argc; i++) { // Drive letter? if (argv[i][0] >= 'A' && argv[i][0] <= 'Z' && argv[i][1] == ':') { drive = 1 + argv[i][0] - 'A'; } else if (argv[i][0] >= 'a' && argv[i][0] <= 'z' && argv[i][1] == ':') { drive = 1 + argv[i][0] - 'a'; } } // Now read the driver settings and current partition i = ioctl_read_control(drive, buffer, sizeof(buffer)); if (i < 0) { perror("ioctl_read_control"); usage(); return EXIT_FAILURE; } if (parse_chudisk_control_string(buffer, i)) { fprintf(stderr, "wrong IOCTL string returned by driver\n"); usage(); return EXIT_FAILURE; } if (scan_disk_partitions() != 0) { return EXIT_FAILURE; } if (num_parts == 0) { fprintf(stderr, "No partitions found on the disk\n"); return EXIT_FAILURE; } printf("Select partition in [1..%u] range: ", num_parts); i = scanf("%u", &part); if (i != 1) { // Assume cancel return EXIT_FAILURE; } if (part == 0 || part > num_parts) { fprintf(stderr, "Invalid partition %u\n", part); return EXIT_FAILURE; } printf("Switching drive to partition %u at offset %lu size %lu\n", part, partinfo[part-1].start_sector, partinfo[part-1].num_sectors); dos_disk_reset(); // Flush caches before we swap the drive... i = 0; buffer[i++] = 'S'; memcpy(&buffer[i], &partinfo[part-1].start_sector, sizeof(uint32_t)); i+=sizeof(uint32_t); buffer[i++] = 'L'; memcpy(&buffer[i], &partinfo[part-1].num_sectors, sizeof(uint32_t)); i+=sizeof(uint32_t); buffer[i] = '\0'; if (ioctl_write_control(drive, buffer, i) < i) { perror("ioctl_write_control"); return EXIT_FAILURE; } // The above IOCTL will also cause the next mediacheck from DOS to return changed. return EXIT_SUCCESS; }