diff options
Diffstat (limited to 'chud.c')
| -rw-r--r-- | chud.c | 418 |
1 files changed, 418 insertions, 0 deletions
@@ -0,0 +1,418 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <dos.h> +#include <dosfunc.h> +#include <io.h> + +#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; +} |
