+typedef enum {SINGLE, START, MIDDLE, END} dbgmsg_t;
+
+void drv_debug(dbgmsg_t phase, const FLASH char *const fmt, ...) \
+{
+ struct cpm_drive_s *dp = &drv_table[drv];
+
+ if (dp->opt & DRV_OPT_DEBUG) {
+
+ va_list ap;
+ va_start (ap, fmt);
+
+ if (phase == SINGLE || phase == START)
+ printf_P(PSTR("# %7lu dsk%d: "), get_timer(0), drv);
+
+ vfprintf_P (stdout, fmt, ap);
+
+ if (phase == SINGLE || phase == END)
+ putc('\n', stdout);
+
+ va_end (ap);
+ }
+}
+
+int drv_list(void)
+{
+ for (uint8_t i = 0; i < CONFIG_CPM_MAX_DRIVE; i++) {
+ struct cpm_drive_s * p = &drv_table[i];
+ if (p->img_name) {
+ printf_P(PSTR(" dsk%d: %2S %3S attached to %s\n"), i,
+ p->opt&DRV_OPT_RO ? PSTR("RO") : PSTR("RW"),
+ p->opt&DRV_OPT_DEBUG ? PSTR("DBG") : PSTR(""),
+ p->img_name);
+ }
+ }
+ return 0;
+}
+
+int drv_detach(uint8_t unit)
+{
+ drv = unit;
+ if (drv < CONFIG_CPM_MAX_DRIVE) {
+ struct cpm_drive_s *p = &drv_table[drv];
+
+ drv_debug(SINGLE, PSTR("detach from '%s'"), p->img_name ? p->img_name : "-");
+
+ if (p->img_name) {
+ f_close(&p->fd);
+ free(p->img_name);
+ p->opt = 0;
+ p->flags &= ~DRV_FLG_DIRTY;
+ p->img_name = NULL;
+
+ uint32_t scb = getenv_ulong(PSTR(ENV_CPM3_SCB), 16, 0);
+ if (scb && (z80_bus_cmd(Request) & ZST_ACQUIRED)) {
+ z80_write(scb + 0xf0, 0xff);
+ z80_write(p->dph + 11, 0xff);
+ z80_bus_cmd(Release);
+ }
+ }
+ }
+ return 0;
+}
+
+static int drv_find_file_attached(const char *fn)
+{
+ for (uint8_t i = 0; i < CONFIG_CPM_MAX_DRIVE; i++) {
+ struct cpm_drive_s *p = &drv_table[i];
+ if (p->img_name && !strcmp(fn, p->img_name)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int drv_attach(uint8_t unit, const char *filename, drv_opt_t options)
+{
+ int res;
+
+ drv = unit;
+ if (drv >= CONFIG_CPM_MAX_DRIVE)
+ return EATRANGE;
+
+ struct cpm_drive_s *p = &drv_table[drv];
+
+ if (options & DRV_OPT_REATTATCH) {
+ if (filename) {
+ return EUNEXPARG;
+ }
+
+ if (!p->img_name) {
+ return EATNOT;
+ }
+
+ /* change options */
+ if ((p->opt ^ options) & DRV_OPT_RO) {
+ f_close(&p->fd);
+ res = f_open(&p->fd, p->img_name,
+ FA_READ | (options&DRV_OPT_RO ? 0 : FA_WRITE));
+ }
+
+ p->opt = options & ~DRV_OPT_REATTATCH;
+
+ } else {
+
+ if (p->img_name)
+ return EATALRDY;
+ if (drv_find_file_attached(filename) >= 0)
+ return EATOTHER;
+
+ p->opt = options;
+
+ /* new attachment */
+
+ if ((p->img_name = strdup(filename)) == NULL)
+ return ENOMEM;
+
+ res = f_open(&p->fd, p->img_name,
+ FA_READ | (options&DRV_OPT_RO ? 0 : FA_WRITE));
+
+ if (!res && f_size(&p->fd) < CONFIG_CPM_DISKSIZE) {
+#if 0
+ unsigned int bw;
+ debug_cpmsd(" expanding image file from %ld to %ld\n",
+ f_size(&p->fd), CONFIG_CPM_DISKSIZE);
+
+ res = f_lseek(&p->fd, CONFIG_CPM_DISKSIZE-CONFIG_CPM_BLOCK_SIZE);
+ if (!res) {
+ memset(disk_buffer, 0xe5, CONFIG_CPM_BLOCK_SIZE);
+ res = f_write(&p->fd, disk_buffer, CONFIG_CPM_BLOCK_SIZE, &bw);
+ if (res || bw < CONFIG_CPM_BLOCK_SIZE) {
+ debug_cpmsd(" failed! res: %d, bytes written: %u\n", res, bw);
+ }
+ p->flags |= DRV_FLG_DIRTY;
+ bg_setstat(handle_cpm_drv_to, 1);
+ }
+#else
+ drv_debug(SINGLE, PSTR("wrong image file size: %ld, should be %ld"),
+ f_size(&p->fd), CONFIG_CPM_DISKSIZE);
+ res = 64;
+#endif
+ }
+ if (res) {
+ drv_detach(drv);
+ return EATOPEN;
+ }
+ }
+
+ return ESUCCESS;
+}
+