+/* echo message */
+void do_msg_echo(uint8_t subf, int len, uint8_t * msg)
+{
+ (void)subf;
+
+ /* send re-echo */
+ msg_xmit(1, 3, len, msg);
+}
+
+/* ---------------------------------------------------------------------------*/
+
+#define MAX_DRIVE 4
+#define BLOCK_SIZE 512
+#define TPA_BASE 0x10000
+#define COMMON_BASE 0xC000
+
+struct cpm_drive_s {
+ uint8_t drv;
+ uint8_t device;
+ char *img_name;
+ FIL fd;
+};
+
+static uint8_t disk_buffer[BLOCK_SIZE];
+static struct cpm_drive_s drv_table[MAX_DRIVE];
+
+/*
+ db 2 ; disk command
+ ds 1 ; subcommand (login/read/write)
+ ds 1 ; @adrv (8 bits) +0
+ ds 1 ; @rdrv (8 bits) +1
+ ds 3 ; @xdph (24 bits) +2
+*/
+
+void do_msg_cpm_login(uint8_t subf, int len, uint8_t * msg)
+{
+
+ FRESULT res = 0;
+ uint8_t rc = 0;
+ uint8_t drv;
+ char *np;
+ uint8_t result_msg[3];
+
+ (void)subf;
+
+ if (len != 5) { /* TODO: check adrv, rdrv */
+ rc = 0x01;
+ goto out;
+ }
+
+ debug_cpmsd("\n## %7lu login: %c:\n", get_timer(0), msg[0]+'A');
+
+
+ drv = msg[0];
+ if ( drv>= MAX_DRIVE) {
+ rc = 0x02;
+ goto out;
+ }
+
+/*
+ uint32_t dph = ((uint32_t)msg[4] << 16) + ((uint16_t)msg[3] << 8) + msg[2];
+*/
+
+ if (drv_table[drv].img_name != NULL) {
+ debug_cpmsd("## %7lu close: '%s'\n", get_timer(0), drv_table[drv].img_name);
+ f_close(&drv_table[drv].fd);
+ free(drv_table[drv].img_name);
+ drv_table[drv].img_name = NULL;
+ }
+
+ strcpy_P((char *)disk_buffer, PSTR("dsk0"));
+ disk_buffer[3] = msg[0] + '0';
+ if (((np = getenv((char*)disk_buffer)) == NULL) ||
+ ((drv_table[drv].img_name = strdup(np)) == NULL)) {
+ rc = 0x03;
+ goto out;
+ }
+
+
+ res = f_open(&drv_table[drv].fd, drv_table[drv].img_name,
+ FA_WRITE | FA_READ);
+
+ debug_cpmsd("## %7lu open: '%s', (env: '%s'), res: %d\n", get_timer(0),
+ drv_table[drv].img_name, disk_buffer, res);
+
+out:
+
+ if (res)
+ rc |= 0x80;
+
+ result_msg[0] = rc;
+ result_msg[1] = res;
+ result_msg[2] = res >> 8;
+
+ if (rc) {
+ debug_cpmsd("## %7lu error rc: %.02x, res: %d\n", get_timer(0), rc, res);
+ };
+
+ /* send result*/
+ msg_xmit(2, subf, sizeof(result_msg), result_msg);
+}
+
+
+/*
+ db 2 ; disk command
+ ds 1 ; subcommand (login/read/write)
+ ds 1 ; @adrv (8 bits) +0
+ ds 1 ; @rdrv (8 bits) +1
+ ds 2 ; @trk (16 bits) +2
+ ds 2 ; @sect(16 bits) +4
+ ds 1 ; @cnt (8 bits) +6
+ ds 3 ; phys. transfer addr +7
+*/
+
+#define ADRV 0
+#define RDRV 1
+#define TRK 2
+#define SEC 4
+#define CNT 6
+#define ADDR 7
+
+void do_msg_cpm_rw(uint8_t subf, int len, uint8_t * msg)
+{
+ uint8_t drv;
+ uint32_t addr;
+ uint32_t pos;
+ uint8_t secs;
+ bool dowrite = (subf == 2);
+ FRESULT res = 0;
+ uint8_t rc = 0;
+ bool buserr = 0;
+ uint8_t result_msg[3];
+
+ if (len != 10) { /* TODO: check adrv, rdrv */
+ rc = 0x01;
+ goto out;
+ }
+
+ drv = msg[ADRV];
+ if ( drv>= MAX_DRIVE) {
+ rc = 0x02;
+ goto out;
+ }
+
+ secs = msg[CNT];
+ addr = ((uint32_t)msg[ADDR+2] << 16) + ((uint16_t)msg[ADDR+1] << 8) + msg[ADDR];
+
+
+ /* TODO: tracks per sector from dpb */
+ pos = (((uint16_t)(msg[TRK+1] << 8) + msg[TRK]) * 8
+ + ((uint32_t)(msg[SEC+1] << 8) + msg[SEC])) * BLOCK_SIZE;
+
+ debug_cpmsd("## %7lu cpm_rw: %s %c: trk:%4d, sec: %d, pos: %.8lx, secs: %2d, "
+ "addr: %.5lx\n", get_timer(0), dowrite ? "write" : " read",
+ msg[ADRV]+'A', ((uint16_t)(msg[TRK+1] << 8) + msg[TRK]), msg[SEC],
+ pos, msg[CNT], addr);
+
+ res = f_lseek(&drv_table[drv].fd, pos);
+ while (!res && secs--) {
+ unsigned int cnt, br;
+
+ /* check bank boundary crossing */
+ cnt = 0;
+ if (addr < (TPA_BASE + COMMON_BASE) &&
+ (addr + BLOCK_SIZE) > (TPA_BASE + COMMON_BASE)) {
+ cnt = (TPA_BASE + COMMON_BASE) - addr;
+ }
+
+ if (cnt) {
+ debug_cpmsd("## %67c addr: %.5lx, cnt: %3d\n", ' ', addr, cnt);
+ debug_cpmsd("## %67c addr: %.5lx, cnt: %3d\n", ' ', addr+cnt-TPA_BASE, BLOCK_SIZE-cnt);
+ }
+
+ if (dowrite) {
+ if (!(z80_bus_cmd(Request) & ZST_ACQUIRED)) {
+ buserr = 1;
+ break;
+ } else {
+ if (cnt) {
+ z80_read_block(disk_buffer, addr, cnt);
+ addr = addr + cnt - TPA_BASE;
+ }
+ z80_read_block(disk_buffer+cnt, addr, BLOCK_SIZE - cnt);
+ z80_bus_cmd(Release);
+ }
+ res = f_write(&drv_table[drv].fd, disk_buffer, BLOCK_SIZE, &br);
+ } else {
+ res = f_read(&drv_table[drv].fd, disk_buffer, BLOCK_SIZE, &br);
+ if (res == FR_OK && br == BLOCK_SIZE) {
+ if (!(z80_bus_cmd(Request) & ZST_ACQUIRED)) {
+ buserr = 1;
+ break;
+ } else {
+ if (cnt) {
+ z80_write_block(disk_buffer, addr, cnt);
+ addr = addr + cnt - TPA_BASE;
+ }
+ z80_write_block(disk_buffer+cnt, addr, BLOCK_SIZE - cnt);
+ z80_bus_cmd(Release);
+ }
+ }
+ }
+
+ if (br != BLOCK_SIZE) {
+ debug_cpmsd("## %7lu f_read res: %d, bytes rd/wr: %u\n", get_timer(0), res, br);
+ dump_ram(disk_buffer, 0, 64, "Read Data");
+ res = -1;
+ }
+
+ addr += BLOCK_SIZE;
+ }
+
+ if (dowrite && !res)
+ res = f_sync(&drv_table[drv].fd);
+
+out:
+ if (buserr) {
+ debug_cpmsd("Bus timeout\n");
+ rc = 0x03;
+ }
+ if (res)
+ rc |= 0x80;
+
+ result_msg[0] = rc;
+ result_msg[1] = res;
+ result_msg[2] = res >> 8;
+
+ if (rc) {
+ debug_cpmsd("###%7lu error rc: %.02x, res: %d\n", get_timer(0), rc, res);
+ }
+
+ /* send result*/
+ msg_xmit(2, subf, sizeof(result_msg), result_msg);
+}
+