Index: emu/all.h
===================================================================
--- emu/all.h	(revision 1efc42c86bda0ca43906d7ba53e9155f41293a6d)
+++ emu/all.h	(revision c5b6c90c4ed6faebc6ff7dc5cffe12481a279503)
@@ -45,7 +45,4 @@
 
 #define ARRAY_COUNT(_a) (int32_t)(sizeof (_a) / sizeof (_a)[0])
-
-#define WIN_W (1520 * 2 / 3)
-#define WIN_H (950 * 2 / 3)
 
 extern int32_t sdl_verbose;
@@ -115,4 +112,7 @@
 extern void fdd_write(uint32_t off, int32_t sz, uint32_t val);
 
+extern void fdd_set_side(int32_t sid);
+extern void fdd_set_sel(int32_t sel);
+
 extern void snd_init(void);
 extern void snd_quit(void);
Index: emu/cpu.c
===================================================================
--- emu/cpu.c	(revision 1efc42c86bda0ca43906d7ba53e9155f41293a6d)
+++ emu/cpu.c	(revision c5b6c90c4ed6faebc6ff7dc5cffe12481a279503)
@@ -379,4 +379,12 @@
 	}
 
+	// handle loading midas.abs
+
+	if (addr == APP_START) {
+		ram_rw_beg = APP_START;
+		ram_rw_end = RAM_START + RAM_SIZE;
+		return;
+	}
+
 	fail("invalid write 0x%08x:8 0x%02x", addr, val);
 }
Index: emu/fdd.c
===================================================================
--- emu/fdd.c	(revision 1efc42c86bda0ca43906d7ba53e9155f41293a6d)
+++ emu/fdd.c	(revision c5b6c90c4ed6faebc6ff7dc5cffe12481a279503)
@@ -26,10 +26,114 @@
 #define N_CYL 80
 #define N_SID 2
-#define N_SEC 18
+#define N_SEC 9
 #define SZ_SEC 512
 
 #define SZ_DISK (N_CYL * N_SID * N_SEC * SZ_SEC)
 
+#define REG_COM_STAT 0
+#define REG_TRA 1
+#define REG_SEC 2
+#define REG_DAT 3
+
+#define COM_REST 0x00
+#define COM_SEEK 0x10
+#define COM_SEEK_VER 0x14
+#define COM_RD_SEC 0x80
+#define COM_RD_SEC_MUL 0x90
+#define COM_WR_SEC_WP 0xa0
+#define COM_WR_SEC 0xa2
+#define COM_INT 0xd0
+#define COM_WR_TRA 0xf0
+
+#define COM_LAT_CYC 10
+#define COM_EXE_CYC 10
+
+typedef enum {
+	STEP_IDLE,
+	STEP_PREP,
+	STEP_EXEC
+} step_t;
+
+typedef struct {
+	int32_t reg_tra;
+	int32_t reg_sec;
+	int32_t reg_dat;
+	int32_t sid;
+	step_t step;
+	int32_t com;
+	int32_t cyc;
+	uint8_t *dat;
+	bool tra_0;
+} state_t;
+
+static state_t state = {
+		.reg_tra = 0, .reg_sec = 0, .reg_dat = 0, .sid = 0,
+		.step = STEP_IDLE, .com = -1, .cyc = 0, .dat = NULL, .tra_0 = false
+};
+
 static uint8_t image[SZ_DISK];
+
+static const char *com_string(int32_t com)
+{
+	switch (com) {
+	case COM_REST:
+		return "COM_REST";
+
+	case COM_SEEK:
+		return "COM_SEEK";
+
+	case COM_SEEK_VER:
+		return "COM_SEEK_VER";
+
+	case COM_RD_SEC:
+		return "COM_RD_SEC";
+
+	case COM_RD_SEC_MUL:
+		return "COM_RD_SEC_MUL";
+
+	case COM_WR_SEC_WP:
+		return "COM_WR_SEC_WP";
+
+	case COM_WR_SEC:
+		return "COM_WR_SEC";
+
+	case COM_WR_TRA:
+		return "COM_WR_TRA";
+
+	default:
+		fail("unknown command 0x%02x", com);
+	}
+}
+
+static void stat_string(int32_t stat, char *buff) {
+	buff[0] = 0;
+
+	if ((stat & 0x80) != 0) {
+		strcat(buff, " mot_on");
+	}
+
+	if ((stat & 0x04) != 0) {
+		strcat(buff, " zero");
+	}
+
+	if ((stat & 0x02) != 0) {
+		strcat(buff, " dat_req");
+	}
+
+	if ((stat & 0x01) != 0) {
+		strcat(buff, " busy");
+	}
+}
+
+void fdd_set_side(int32_t sid)
+{
+	ver2("sid <- %d", sid);
+	state.sid = sid;
+}
+
+void fdd_set_sel(int32_t sel)
+{
+	ver2("sel <- %d", sel);
+}
 
 void fdd_init(void)
@@ -67,4 +171,33 @@
 {
 	ver3("fdd exec");
+
+	switch (state.step) {
+	case STEP_IDLE:
+		break;
+
+	case STEP_PREP:
+		ver3("prep %d", state.cyc);
+		--state.cyc;
+
+		if (state.cyc == 0) {
+			ver2("exec %s", com_string(state.com));
+			state.step = STEP_EXEC;
+			state.cyc = COM_EXE_CYC;
+		}
+
+		break;
+
+	case STEP_EXEC:
+		ver3("exec %d", state.cyc);
+		--state.cyc;
+
+		if (state.cyc == 0) {
+			ver2("idle %s", com_string(state.com));
+			state.step = STEP_IDLE;
+		}
+
+		break;
+	}
+
 	return false;
 }
@@ -72,10 +205,169 @@
 uint32_t fdd_read(uint32_t off, int32_t sz)
 {
-	ver2("fdd rd %u:%d", off, sz * 8);
-	return 0;
+	ver3("fdd rd %u:%d", off, sz * 8);
+
+	if (sz != 1 || off > 3) {
+		fail("invalid fdd rd %u:%d", off, sz * 8);
+	}
+
+	uint32_t rv;
+
+	switch (off) {
+	case REG_COM_STAT:
+		rv = 0x80; // motor on
+
+		if (state.step == STEP_EXEC) {
+			rv |= 0x01; // busy
+
+			switch (state.com) {
+			case COM_RD_SEC:
+			case COM_RD_SEC_MUL:
+			case COM_WR_SEC:
+			case COM_WR_SEC_WP:
+				rv |= 0x02; // data request
+				break;
+			}
+		}
+
+		if (state.tra_0) {
+			rv |= 0x04; // track zero
+		}
+
+		char stat[100];
+		stat_string((int32_t)rv, stat);
+		ver3("stat -> 0x%02x%s", rv, stat);
+		break;
+
+	case REG_TRA:
+		rv = (uint32_t)state.reg_tra;
+		ver2("tra -> %u", rv);
+		break;
+
+	case REG_SEC:
+		rv = (uint32_t)state.reg_sec;
+		ver2("sec -> %u", rv);
+		break;
+
+	case REG_DAT:
+		if (state.step != STEP_EXEC ||
+				(state.com != COM_RD_SEC && state.com != COM_RD_SEC_MUL)) {
+			fail("unexpected data register read");
+		}
+
+		rv = *state.dat;
+		size_t addr = (size_t)(state.dat - image);
+
+		if ((addr & (SZ_SEC - 1)) == 0) {
+			ver2("addr 0x%06zx -> 0x%02x", addr, rv);
+		}
+		else {
+			ver3("addr 0x%06zx -> 0x%02x", addr, rv);
+		}
+
+		++state.dat;
+		state.cyc = COM_EXE_CYC;
+		break;
+
+	default:
+		rv = 0;
+		break;
+	}
+
+	return rv;
 }
 
 void fdd_write(uint32_t off, int32_t sz, uint32_t val)
 {
-	ver2("fdd wr %u:%d 0x%0*x", off, sz * 8, sz * 2, val);
-}
+	ver3("fdd wr %u:%d 0x%0*x", off, sz * 8, sz * 2, val);
+
+	if (sz != 1 || off > 3) {
+		fail("invalid fdd wr %u:%d", off, sz * 8);
+	}
+
+	switch (off) {
+	case REG_COM_STAT:
+		ver2("com <- 0x%02x, tra %d, sid %d, sec %d",
+				val, state.reg_tra, state.sid, state.reg_sec);
+
+		state.com = (int32_t)val;
+
+		ver2("prep %s", com_string(state.com));
+		state.step = STEP_PREP;
+		state.cyc = COM_LAT_CYC;
+
+		switch (val) {
+		case COM_REST:
+			state.reg_tra = 0;
+			state.tra_0 = true;
+			break;
+
+		case COM_SEEK:
+		case COM_SEEK_VER:
+			state.reg_tra = state.reg_dat;
+			state.tra_0 = state.reg_tra == 0;
+			break;
+
+		case COM_RD_SEC:
+		case COM_RD_SEC_MUL:
+		case COM_WR_SEC:
+		case COM_WR_SEC_WP: {
+			size_t sec_off = (size_t)(((state.reg_tra * N_SID + state.sid) * N_SEC +
+					state.reg_sec - 1) * SZ_SEC);
+			state.dat = image + sec_off;
+			state.tra_0 = false;
+			break;
+		}
+
+		case COM_INT:
+			state.step = STEP_IDLE;
+			state.com = -1;
+			state.cyc = 0;
+			state.dat = NULL;
+			state.tra_0 = false;
+			break;
+
+		case COM_WR_TRA:
+			state.tra_0 = false;
+			fail("format not yet supported");
+			break;
+		}
+
+		break;
+
+	case REG_TRA:
+		state.reg_tra = (int32_t)val;
+		ver2("tra <- %u", val);
+		break;
+
+	case REG_SEC:
+		state.reg_sec = (int32_t)val;
+		ver2("sec <- %u", val);
+		break;
+
+	case REG_DAT:
+		if (state.step == STEP_EXEC &&
+				(state.com == COM_WR_SEC || state.com == COM_WR_SEC_WP)) {
+			*state.dat = (uint8_t)val;
+			size_t addr = (size_t)(state.dat - image);
+
+			if ((addr & (SZ_SEC - 1)) == 0) {
+				ver2("addr 0x%06zx <- 0x%02x", addr, val);
+			}
+			else {
+				ver3("addr 0x%06zx <- 0x%02x", addr, val);
+			}
+
+			++state.dat;
+			state.cyc = COM_EXE_CYC;
+		}
+		else {
+			state.reg_dat = (int32_t)val;
+			ver2("dat <- 0x%02x", val);
+		}
+
+		break;
+
+	default:
+		break;
+	}
+}
Index: emu/mid.c
===================================================================
--- emu/mid.c	(revision 1efc42c86bda0ca43906d7ba53e9155f41293a6d)
+++ emu/mid.c	(revision c5b6c90c4ed6faebc6ff7dc5cffe12481a279503)
@@ -23,4 +23,9 @@
 
 int32_t mid_verbose = 0;
+
+#define REG_IER_ISR 0
+#define REG_CFR_SR  1
+#define REG_CDR_TBR 2
+#define REG_TDR_RDR 3
 
 void mid_init(void)
@@ -49,3 +54,27 @@
 {
 	ver2("mid wr %u:%d 0x%0*x", off, sz * 8, sz * 2, val);
+
+	if (sz != 1 || off > 7) {
+		fail("invalid mid wr %u:%d", off, sz * 8);
+	}
+
+	int32_t rg = (int32_t)(off % 4);
+	int32_t un = (int32_t)(off / 4);
+
+	switch (rg) {
+	case REG_CFR_SR:
+		ver2("CFR[%d] 0x%02x", un, val);
+
+		if (un == 1) {
+			fdd_set_side((int32_t)val & 0x01);
+		}
+		else {
+			fdd_set_sel((int32_t)val & 0x01);
+		}
+
+		break;
+
+	default:
+		break;
+	}
 }
Index: emu/mkdisk.c
===================================================================
--- emu/mkdisk.c	(revision 1efc42c86bda0ca43906d7ba53e9155f41293a6d)
+++ emu/mkdisk.c	(revision c5b6c90c4ed6faebc6ff7dc5cffe12481a279503)
@@ -30,12 +30,13 @@
 #define N_CYL 80
 #define N_SID 2
-#define N_SEC 18
+#define N_SEC 9
 #define SZ_SEC 512
 
-#define N_SEC_CLU 1
+#define N_SEC_CLU 2
+#define SZ_CLU (SZ_SEC * N_SEC_CLU)
 
 #define N_BOOT_SEC 1
-#define N_FAT_SEC 9
-#define N_ROOT_SEC 14
+#define N_FAT_SEC 3
+#define N_ROOT_SEC 7
 
 #define N_FAT 2
@@ -142,6 +143,6 @@
 	off = put_sec_16(0, off, N_CYL * N_SID * N_SEC);
 
-	// media descriptor (floppy disk, 1.44M)
-	off = put_sec_8(0, off, 0xf0);
+	// media descriptor (floppy disk, 720k)
+	off = put_sec_8(0, off, 0xf9);
 
 	// sectors per FAT (9 * 512 / 3 * 2 = 3072)
@@ -165,10 +166,10 @@
 	int32_t ent = 0;
 
-	ent = put_fat_12(sec, ent, 0xff0);
+	ent = put_fat_12(sec, ent, 0xff9);
 	ent = put_fat_12(sec, ent, 0xfff);
 
-	int32_t n_sec = (sz_midas + SZ_SEC - 1) / SZ_SEC;
-
-	for (int32_t i = 0; i < n_sec - 1; ++i) {
+	int32_t n_clu = (sz_midas + SZ_CLU - 1) / SZ_CLU;
+
+	for (int32_t i = 0; i < n_clu - 1; ++i) {
 		ent = put_fat_12(sec, ent, ent + 1);
 	}
Index: emu/ser.c
===================================================================
--- emu/ser.c	(revision 1efc42c86bda0ca43906d7ba53e9155f41293a6d)
+++ emu/ser.c	(revision c5b6c90c4ed6faebc6ff7dc5cffe12481a279503)
@@ -22,4 +22,9 @@
 #define ver3(...) _ver(ser_verbose, 2, __VA_ARGS__)
 
+int32_t ser_verbose = 0;
+
+#define WIN_W (1520 * 2 / 3)
+#define WIN_H (950 * 2 / 3)
+
 #define BEL_CYC 10000
 
@@ -45,6 +50,4 @@
 	uint8_t rdr;
 } state_t;
-
-int32_t ser_verbose = 0;
 
 static state_t state[] = {
