* way. There are changes in DOR register and DMA is not available.
*/
+#include "qemu/osdep.h"
#include "hw/hw.h"
#include "hw/block/fdc.h"
+#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/timer.h"
#include "hw/isa/isa.h"
/********************************************************/
/* debug Floppy devices */
-//#define DEBUG_FLOPPY
-#ifdef DEBUG_FLOPPY
+#define DEBUG_FLOPPY 0
+
#define FLOPPY_DPRINTF(fmt, ...) \
- do { printf("FLOPPY: " fmt , ## __VA_ARGS__); } while (0)
-#else
-#define FLOPPY_DPRINTF(fmt, ...)
-#endif
+ do { \
+ if (DEBUG_FLOPPY) { \
+ fprintf(stderr, "FLOPPY: " fmt , ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+
+/********************************************************/
+/* qdev floppy bus */
+
+#define TYPE_FLOPPY_BUS "floppy-bus"
+#define FLOPPY_BUS(obj) OBJECT_CHECK(FloppyBus, (obj), TYPE_FLOPPY_BUS)
+
+typedef struct FDCtrl FDCtrl;
+typedef struct FDrive FDrive;
+static FDrive *get_drv(FDCtrl *fdctrl, int unit);
+
+typedef struct FloppyBus {
+ BusState bus;
+ FDCtrl *fdc;
+} FloppyBus;
+
+static const TypeInfo floppy_bus_info = {
+ .name = TYPE_FLOPPY_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(FloppyBus),
+};
+
+static void floppy_bus_create(FDCtrl *fdc, FloppyBus *bus, DeviceState *dev)
+{
+ qbus_create_inplace(bus, sizeof(FloppyBus), TYPE_FLOPPY_BUS, dev, NULL);
+ bus->fdc = fdc;
+}
+
/********************************************************/
/* Floppy drive emulation */
FDRIVE_RATE_1M = 0x03, /* 1 Mbps */
} FDriveRate;
+typedef enum FDriveSize {
+ FDRIVE_SIZE_UNKNOWN,
+ FDRIVE_SIZE_350,
+ FDRIVE_SIZE_525,
+} FDriveSize;
+
typedef struct FDFormat {
- FDriveType drive;
+ FloppyDriveType drive;
uint8_t last_sect;
uint8_t max_track;
uint8_t max_head;
FDriveRate rate;
} FDFormat;
+/* In many cases, the total sector size of a format is enough to uniquely
+ * identify it. However, there are some total sector collisions between
+ * formats of different physical size, and these are noted below by
+ * highlighting the total sector size for entries with collisions. */
static const FDFormat fd_formats[] = {
/* First entry is default format */
/* 1.44 MB 3"1/2 floppy disks */
- { FDRIVE_DRV_144, 18, 80, 1, FDRIVE_RATE_500K, },
- { FDRIVE_DRV_144, 20, 80, 1, FDRIVE_RATE_500K, },
- { FDRIVE_DRV_144, 21, 80, 1, FDRIVE_RATE_500K, },
- { FDRIVE_DRV_144, 21, 82, 1, FDRIVE_RATE_500K, },
- { FDRIVE_DRV_144, 21, 83, 1, FDRIVE_RATE_500K, },
- { FDRIVE_DRV_144, 22, 80, 1, FDRIVE_RATE_500K, },
- { FDRIVE_DRV_144, 23, 80, 1, FDRIVE_RATE_500K, },
- { FDRIVE_DRV_144, 24, 80, 1, FDRIVE_RATE_500K, },
+ { FLOPPY_DRIVE_TYPE_144, 18, 80, 1, FDRIVE_RATE_500K, }, /* 3.5" 2880 */
+ { FLOPPY_DRIVE_TYPE_144, 20, 80, 1, FDRIVE_RATE_500K, }, /* 3.5" 3200 */
+ { FLOPPY_DRIVE_TYPE_144, 21, 80, 1, FDRIVE_RATE_500K, },
+ { FLOPPY_DRIVE_TYPE_144, 21, 82, 1, FDRIVE_RATE_500K, },
+ { FLOPPY_DRIVE_TYPE_144, 21, 83, 1, FDRIVE_RATE_500K, },
+ { FLOPPY_DRIVE_TYPE_144, 22, 80, 1, FDRIVE_RATE_500K, },
+ { FLOPPY_DRIVE_TYPE_144, 23, 80, 1, FDRIVE_RATE_500K, },
+ { FLOPPY_DRIVE_TYPE_144, 24, 80, 1, FDRIVE_RATE_500K, },
/* 2.88 MB 3"1/2 floppy disks */
- { FDRIVE_DRV_288, 36, 80, 1, FDRIVE_RATE_1M, },
- { FDRIVE_DRV_288, 39, 80, 1, FDRIVE_RATE_1M, },
- { FDRIVE_DRV_288, 40, 80, 1, FDRIVE_RATE_1M, },
- { FDRIVE_DRV_288, 44, 80, 1, FDRIVE_RATE_1M, },
- { FDRIVE_DRV_288, 48, 80, 1, FDRIVE_RATE_1M, },
+ { FLOPPY_DRIVE_TYPE_288, 36, 80, 1, FDRIVE_RATE_1M, },
+ { FLOPPY_DRIVE_TYPE_288, 39, 80, 1, FDRIVE_RATE_1M, },
+ { FLOPPY_DRIVE_TYPE_288, 40, 80, 1, FDRIVE_RATE_1M, },
+ { FLOPPY_DRIVE_TYPE_288, 44, 80, 1, FDRIVE_RATE_1M, },
+ { FLOPPY_DRIVE_TYPE_288, 48, 80, 1, FDRIVE_RATE_1M, },
/* 720 kB 3"1/2 floppy disks */
- { FDRIVE_DRV_144, 9, 80, 1, FDRIVE_RATE_250K, },
- { FDRIVE_DRV_144, 10, 80, 1, FDRIVE_RATE_250K, },
- { FDRIVE_DRV_144, 10, 82, 1, FDRIVE_RATE_250K, },
- { FDRIVE_DRV_144, 10, 83, 1, FDRIVE_RATE_250K, },
- { FDRIVE_DRV_144, 13, 80, 1, FDRIVE_RATE_250K, },
- { FDRIVE_DRV_144, 14, 80, 1, FDRIVE_RATE_250K, },
+ { FLOPPY_DRIVE_TYPE_144, 9, 80, 1, FDRIVE_RATE_250K, }, /* 3.5" 1440 */
+ { FLOPPY_DRIVE_TYPE_144, 10, 80, 1, FDRIVE_RATE_250K, },
+ { FLOPPY_DRIVE_TYPE_144, 10, 82, 1, FDRIVE_RATE_250K, },
+ { FLOPPY_DRIVE_TYPE_144, 10, 83, 1, FDRIVE_RATE_250K, },
+ { FLOPPY_DRIVE_TYPE_144, 13, 80, 1, FDRIVE_RATE_250K, },
+ { FLOPPY_DRIVE_TYPE_144, 14, 80, 1, FDRIVE_RATE_250K, },
/* 1.2 MB 5"1/4 floppy disks */
- { FDRIVE_DRV_120, 15, 80, 1, FDRIVE_RATE_500K, },
- { FDRIVE_DRV_120, 18, 80, 1, FDRIVE_RATE_500K, },
- { FDRIVE_DRV_120, 18, 82, 1, FDRIVE_RATE_500K, },
- { FDRIVE_DRV_120, 18, 83, 1, FDRIVE_RATE_500K, },
- { FDRIVE_DRV_120, 20, 80, 1, FDRIVE_RATE_500K, },
+ { FLOPPY_DRIVE_TYPE_120, 15, 80, 1, FDRIVE_RATE_500K, },
+ { FLOPPY_DRIVE_TYPE_120, 18, 80, 1, FDRIVE_RATE_500K, }, /* 5.25" 2880 */
+ { FLOPPY_DRIVE_TYPE_120, 18, 82, 1, FDRIVE_RATE_500K, },
+ { FLOPPY_DRIVE_TYPE_120, 18, 83, 1, FDRIVE_RATE_500K, },
+ { FLOPPY_DRIVE_TYPE_120, 20, 80, 1, FDRIVE_RATE_500K, }, /* 5.25" 3200 */
/* 720 kB 5"1/4 floppy disks */
- { FDRIVE_DRV_120, 9, 80, 1, FDRIVE_RATE_250K, },
- { FDRIVE_DRV_120, 11, 80, 1, FDRIVE_RATE_250K, },
+ { FLOPPY_DRIVE_TYPE_120, 9, 80, 1, FDRIVE_RATE_250K, }, /* 5.25" 1440 */
+ { FLOPPY_DRIVE_TYPE_120, 11, 80, 1, FDRIVE_RATE_250K, },
/* 360 kB 5"1/4 floppy disks */
- { FDRIVE_DRV_120, 9, 40, 1, FDRIVE_RATE_300K, },
- { FDRIVE_DRV_120, 9, 40, 0, FDRIVE_RATE_300K, },
- { FDRIVE_DRV_120, 10, 41, 1, FDRIVE_RATE_300K, },
- { FDRIVE_DRV_120, 10, 42, 1, FDRIVE_RATE_300K, },
+ { FLOPPY_DRIVE_TYPE_120, 9, 40, 1, FDRIVE_RATE_300K, }, /* 5.25" 720 */
+ { FLOPPY_DRIVE_TYPE_120, 9, 40, 0, FDRIVE_RATE_300K, },
+ { FLOPPY_DRIVE_TYPE_120, 10, 41, 1, FDRIVE_RATE_300K, },
+ { FLOPPY_DRIVE_TYPE_120, 10, 42, 1, FDRIVE_RATE_300K, },
/* 320 kB 5"1/4 floppy disks */
- { FDRIVE_DRV_120, 8, 40, 1, FDRIVE_RATE_250K, },
- { FDRIVE_DRV_120, 8, 40, 0, FDRIVE_RATE_250K, },
+ { FLOPPY_DRIVE_TYPE_120, 8, 40, 1, FDRIVE_RATE_250K, },
+ { FLOPPY_DRIVE_TYPE_120, 8, 40, 0, FDRIVE_RATE_250K, },
/* 360 kB must match 5"1/4 better than 3"1/2... */
- { FDRIVE_DRV_144, 9, 80, 0, FDRIVE_RATE_250K, },
+ { FLOPPY_DRIVE_TYPE_144, 9, 80, 0, FDRIVE_RATE_250K, }, /* 3.5" 720 */
/* end */
- { FDRIVE_DRV_NONE, -1, -1, 0, 0, },
+ { FLOPPY_DRIVE_TYPE_NONE, -1, -1, 0, 0, },
};
-static void pick_geometry(BlockBackend *blk, int *nb_heads,
- int *max_track, int *last_sect,
- FDriveType drive_in, FDriveType *drive,
- FDriveRate *rate)
+static FDriveSize drive_size(FloppyDriveType drive)
{
- const FDFormat *parse;
- uint64_t nb_sectors, size;
- int i, first_match, match;
-
- blk_get_geometry(blk, &nb_sectors);
- match = -1;
- first_match = -1;
- for (i = 0; ; i++) {
- parse = &fd_formats[i];
- if (parse->drive == FDRIVE_DRV_NONE) {
- break;
- }
- if (drive_in == parse->drive ||
- drive_in == FDRIVE_DRV_NONE) {
- size = (parse->max_head + 1) * parse->max_track *
- parse->last_sect;
- if (nb_sectors == size) {
- match = i;
- break;
- }
- if (first_match == -1) {
- first_match = i;
- }
- }
- }
- if (match == -1) {
- if (first_match == -1) {
- match = 1;
- } else {
- match = first_match;
- }
- parse = &fd_formats[match];
+ switch (drive) {
+ case FLOPPY_DRIVE_TYPE_120:
+ return FDRIVE_SIZE_525;
+ case FLOPPY_DRIVE_TYPE_144:
+ case FLOPPY_DRIVE_TYPE_288:
+ return FDRIVE_SIZE_350;
+ default:
+ return FDRIVE_SIZE_UNKNOWN;
}
- *nb_heads = parse->max_head + 1;
- *max_track = parse->max_track;
- *last_sect = parse->last_sect;
- *drive = parse->drive;
- *rate = parse->rate;
}
#define GET_CUR_DRV(fdctrl) ((fdctrl)->cur_drv)
#define FD_SECTOR_SC 2 /* Sector size code */
#define FD_RESET_SENSEI_COUNT 4 /* Number of sense interrupts on RESET */
-typedef struct FDCtrl FDCtrl;
-
/* Floppy disk drive emulation */
typedef enum FDiskFlags {
FDISK_DBL_SIDES = 0x01,
} FDiskFlags;
-typedef struct FDrive {
+struct FDrive {
FDCtrl *fdctrl;
BlockBackend *blk;
/* Drive status */
- FDriveType drive;
+ FloppyDriveType drive; /* CMOS drive type */
uint8_t perpendicular; /* 2.88 MB access mode */
/* Position */
uint8_t head;
uint8_t track;
uint8_t sect;
/* Media */
+ FloppyDriveType disk; /* Current disk type */
FDiskFlags flags;
uint8_t last_sect; /* Nb sector per track */
uint8_t max_track; /* Nb of tracks */
uint8_t ro; /* Is read-only */
uint8_t media_changed; /* Is media changed */
uint8_t media_rate; /* Data rate of medium */
-} FDrive;
+
+ bool media_validated; /* Have we validated the media? */
+};
+
+
+static FloppyDriveType get_fallback_drive_type(FDrive *drv);
+
+/* Hack: FD_SEEK is expected to work on empty drives. However, QEMU
+ * currently goes through some pains to keep seeks within the bounds
+ * established by last_sect and max_track. Correcting this is difficult,
+ * as refactoring FDC code tends to expose nasty bugs in the Linux kernel.
+ *
+ * For now: allow empty drives to have large bounds so we can seek around,
+ * with the understanding that when a diskette is inserted, the bounds will
+ * properly tighten to match the geometry of that inserted medium.
+ */
+static void fd_empty_seek_hack(FDrive *drv)
+{
+ drv->last_sect = 0xFF;
+ drv->max_track = 0xFF;
+}
static void fd_init(FDrive *drv)
{
/* Drive */
- drv->drive = FDRIVE_DRV_NONE;
drv->perpendicular = 0;
/* Disk */
+ drv->disk = FLOPPY_DRIVE_TYPE_NONE;
drv->last_sect = 0;
drv->max_track = 0;
+ drv->ro = true;
+ drv->media_changed = 1;
}
#define NUM_SIDES(drv) ((drv)->flags & FDISK_DBL_SIDES ? 2 : 1)
NUM_SIDES(drv));
}
+/* Returns current position, in bytes, for given drive */
+static int fd_offset(FDrive *drv)
+{
+ g_assert(fd_sector(drv) < INT_MAX >> BDRV_SECTOR_BITS);
+ return fd_sector(drv) << BDRV_SECTOR_BITS;
+}
+
/* Seek to a new position:
* returns 0 if already on right track
* returns 1 if track changed
fd_seek(drv, 0, 0, 1, 1);
}
+/**
+ * Determine geometry based on inserted diskette.
+ * Will not operate on an empty drive.
+ *
+ * @return: 0 on success, -1 if the drive is empty.
+ */
+static int pick_geometry(FDrive *drv)
+{
+ BlockBackend *blk = drv->blk;
+ const FDFormat *parse;
+ uint64_t nb_sectors, size;
+ int i;
+ int match, size_match, type_match;
+ bool magic = drv->drive == FLOPPY_DRIVE_TYPE_AUTO;
+
+ /* We can only pick a geometry if we have a diskette. */
+ if (!drv->blk || !blk_is_inserted(drv->blk) ||
+ drv->drive == FLOPPY_DRIVE_TYPE_NONE)
+ {
+ return -1;
+ }
+
+ /* We need to determine the likely geometry of the inserted medium.
+ * In order of preference, we look for:
+ * (1) The same drive type and number of sectors,
+ * (2) The same diskette size and number of sectors,
+ * (3) The same drive type.
+ *
+ * In all cases, matches that occur higher in the drive table will take
+ * precedence over matches that occur later in the table.
+ */
+ blk_get_geometry(blk, &nb_sectors);
+ match = size_match = type_match = -1;
+ for (i = 0; ; i++) {
+ parse = &fd_formats[i];
+ if (parse->drive == FLOPPY_DRIVE_TYPE_NONE) {
+ break;
+ }
+ size = (parse->max_head + 1) * parse->max_track * parse->last_sect;
+ if (nb_sectors == size) {
+ if (magic || parse->drive == drv->drive) {
+ /* (1) perfect match -- nb_sectors and drive type */
+ goto out;
+ } else if (drive_size(parse->drive) == drive_size(drv->drive)) {
+ /* (2) size match -- nb_sectors and physical medium size */
+ match = (match == -1) ? i : match;
+ } else {
+ /* This is suspicious -- Did the user misconfigure? */
+ size_match = (size_match == -1) ? i : size_match;
+ }
+ } else if (type_match == -1) {
+ if ((parse->drive == drv->drive) ||
+ (magic && (parse->drive == get_fallback_drive_type(drv)))) {
+ /* (3) type match -- nb_sectors mismatch, but matches the type
+ * specified explicitly by the user, or matches the fallback
+ * default type when using the drive autodetect mechanism */
+ type_match = i;
+ }
+ }
+ }
+
+ /* No exact match found */
+ if (match == -1) {
+ if (size_match != -1) {
+ parse = &fd_formats[size_match];
+ FLOPPY_DPRINTF("User requested floppy drive type '%s', "
+ "but inserted medium appears to be a "
+ "%"PRId64" sector '%s' type\n",
+ FloppyDriveType_lookup[drv->drive],
+ nb_sectors,
+ FloppyDriveType_lookup[parse->drive]);
+ }
+ match = type_match;
+ }
+
+ /* No match of any kind found -- fd_format is misconfigured, abort. */
+ if (match == -1) {
+ error_setg(&error_abort, "No candidate geometries present in table "
+ " for floppy drive type '%s'",
+ FloppyDriveType_lookup[drv->drive]);
+ }
+
+ parse = &(fd_formats[match]);
+
+ out:
+ if (parse->max_head == 0) {
+ drv->flags &= ~FDISK_DBL_SIDES;
+ } else {
+ drv->flags |= FDISK_DBL_SIDES;
+ }
+ drv->max_track = parse->max_track;
+ drv->last_sect = parse->last_sect;
+ drv->disk = parse->drive;
+ drv->media_rate = parse->rate;
+ return 0;
+}
+
+static void pick_drive_type(FDrive *drv)
+{
+ if (drv->drive != FLOPPY_DRIVE_TYPE_AUTO) {
+ return;
+ }
+
+ if (pick_geometry(drv) == 0) {
+ drv->drive = drv->disk;
+ } else {
+ drv->drive = get_fallback_drive_type(drv);
+ }
+
+ g_assert(drv->drive != FLOPPY_DRIVE_TYPE_AUTO);
+}
+
/* Revalidate a disk drive after a disk change */
static void fd_revalidate(FDrive *drv)
{
- int nb_heads, max_track, last_sect, ro;
- FDriveType drive;
- FDriveRate rate;
+ int rc;
FLOPPY_DPRINTF("revalidate\n");
if (drv->blk != NULL) {
- ro = blk_is_read_only(drv->blk);
- pick_geometry(drv->blk, &nb_heads, &max_track,
- &last_sect, drv->drive, &drive, &rate);
+ drv->ro = blk_is_read_only(drv->blk);
if (!blk_is_inserted(drv->blk)) {
FLOPPY_DPRINTF("No disk in drive\n");
- } else {
- FLOPPY_DPRINTF("Floppy disk (%d h %d t %d s) %s\n", nb_heads,
- max_track, last_sect, ro ? "ro" : "rw");
- }
- if (nb_heads == 1) {
- drv->flags &= ~FDISK_DBL_SIDES;
- } else {
- drv->flags |= FDISK_DBL_SIDES;
+ drv->disk = FLOPPY_DRIVE_TYPE_NONE;
+ fd_empty_seek_hack(drv);
+ } else if (!drv->media_validated) {
+ rc = pick_geometry(drv);
+ if (rc) {
+ FLOPPY_DPRINTF("Could not validate floppy drive media");
+ } else {
+ drv->media_validated = true;
+ FLOPPY_DPRINTF("Floppy disk (%d h %d t %d s) %s\n",
+ (drv->flags & FDISK_DBL_SIDES) ? 2 : 1,
+ drv->max_track, drv->last_sect,
+ drv->ro ? "ro" : "rw");
+ }
}
- drv->max_track = max_track;
- drv->last_sect = last_sect;
- drv->ro = ro;
- drv->drive = drive;
- drv->media_rate = rate;
} else {
FLOPPY_DPRINTF("No drive connected\n");
drv->last_sect = 0;
drv->max_track = 0;
drv->flags &= ~FDISK_DBL_SIDES;
+ drv->drive = FLOPPY_DRIVE_TYPE_NONE;
+ drv->disk = FLOPPY_DRIVE_TYPE_NONE;
+ }
+}
+
+static void fd_change_cb(void *opaque, bool load)
+{
+ FDrive *drive = opaque;
+
+ drive->media_changed = 1;
+ drive->media_validated = false;
+ fd_revalidate(drive);
+}
+
+static const BlockDevOps fd_block_ops = {
+ .change_media_cb = fd_change_cb,
+};
+
+
+#define TYPE_FLOPPY_DRIVE "floppy"
+#define FLOPPY_DRIVE(obj) \
+ OBJECT_CHECK(FloppyDrive, (obj), TYPE_FLOPPY_DRIVE)
+
+typedef struct FloppyDrive {
+ DeviceState qdev;
+ uint32_t unit;
+} FloppyDrive;
+
+static Property floppy_drive_properties[] = {
+ DEFINE_PROP_UINT32("unit", FloppyDrive, unit, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static int floppy_drive_init(DeviceState *qdev)
+{
+ FloppyDrive *dev = FLOPPY_DRIVE(qdev);
+ FloppyBus *bus = FLOPPY_BUS(qdev->parent_bus);
+ FDrive *drive;
+
+ if (dev->unit == -1) {
+ for (dev->unit = 0; dev->unit < MAX_FD; dev->unit++) {
+ drive = get_drv(bus->fdc, dev->unit);
+ if (!drive->blk) {
+ break;
+ }
+ }
+ }
+
+ if (dev->unit >= MAX_FD) {
+ error_report("Can't create floppy unit %d, bus supports only %d units",
+ dev->unit, MAX_FD);
+ return -1;
+ }
+
+ /* TODO Check whether unit is in use */
+
+ drive = get_drv(bus->fdc, dev->unit);
+
+ if (drive->blk) {
+ if (blk_get_on_error(drive->blk, 0) != BLOCKDEV_ON_ERROR_ENOSPC) {
+ error_report("fdc doesn't support drive option werror");
+ return -1;
+ }
+ if (blk_get_on_error(drive->blk, 1) != BLOCKDEV_ON_ERROR_REPORT) {
+ error_report("fdc doesn't support drive option rerror");
+ return -1;
+ }
+ } else {
+ /* Anonymous BlockBackend for an empty drive */
+ drive->blk = blk_new();
}
+
+ fd_init(drive);
+ if (drive->blk) {
+ blk_set_dev_ops(drive->blk, &fd_block_ops, drive);
+ pick_drive_type(drive);
+ }
+ fd_revalidate(drive);
+
+ return 0;
}
+static void floppy_drive_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->init = floppy_drive_init;
+ set_bit(DEVICE_CATEGORY_STORAGE, k->categories);
+ k->bus_type = TYPE_FLOPPY_BUS;
+ k->props = floppy_drive_properties;
+ k->desc = "virtual floppy drive";
+}
+
+static const TypeInfo floppy_drive_info = {
+ .name = TYPE_FLOPPY_DRIVE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(FloppyDrive),
+ .class_init = floppy_drive_class_init,
+};
+
/********************************************************/
/* Intel 82078 floppy disk controller emulation */
QEMUTimer *result_timer;
int dma_chann;
uint8_t phase;
+ IsaDma *dma;
/* Controller's identification */
uint8_t version;
/* HW */
/* Power down config (also with status regB access mode */
uint8_t pwrd;
/* Floppy drives */
+ FloppyBus bus;
uint8_t num_floppies;
FDrive drives[MAX_FD];
int reset_sensei;
uint32_t check_media_rate;
+ FloppyDriveType fallback; /* type=auto failure fallback */
/* Timers state */
uint8_t timer0;
uint8_t timer1;
+ PortioList portio_list;
};
+static FloppyDriveType get_fallback_drive_type(FDrive *drv)
+{
+ return drv->fdctrl->fallback;
+}
+
#define TYPE_SYSBUS_FDC "base-sysbus-fdc"
#define SYSBUS_FDC(obj) OBJECT_CHECK(FDCtrlSysBus, (obj), TYPE_SYSBUS_FDC)
}
#endif
-static FDrive *get_cur_drv(FDCtrl *fdctrl)
+static FDrive *get_drv(FDCtrl *fdctrl, int unit)
{
- switch (fdctrl->cur_drv) {
+ switch (unit) {
case 0: return drv0(fdctrl);
case 1: return drv1(fdctrl);
#if MAX_FD == 4
}
}
+static FDrive *get_cur_drv(FDCtrl *fdctrl)
+{
+ return get_drv(fdctrl, fdctrl->cur_drv);
+}
+
/* Status A register : 0x00 (read-only) */
static uint32_t fdctrl_read_statusA(FDCtrl *fdctrl)
{
fdctrl->fifo[6] = FD_SECTOR_SC;
fdctrl->data_dir = FD_DIR_READ;
if (!(fdctrl->msr & FD_MSR_NONDMA)) {
- DMA_release_DREQ(fdctrl->dma_chann);
+ IsaDmaClass *k = ISADMA_GET_CLASS(fdctrl->dma);
+ k->release_DREQ(fdctrl->dma, fdctrl->dma_chann);
}
fdctrl->msr |= FD_MSR_RQM | FD_MSR_DIO;
fdctrl->msr &= ~FD_MSR_NONDMA;
}
fdctrl->eot = fdctrl->fifo[6];
if (fdctrl->dor & FD_DOR_DMAEN) {
- int dma_mode;
+ IsaDmaTransferMode dma_mode;
+ IsaDmaClass *k = ISADMA_GET_CLASS(fdctrl->dma);
+ bool dma_mode_ok;
/* DMA transfer are enabled. Check if DMA channel is well programmed */
- dma_mode = DMA_get_channel_mode(fdctrl->dma_chann);
- dma_mode = (dma_mode >> 2) & 3;
+ dma_mode = k->get_transfer_mode(fdctrl->dma, fdctrl->dma_chann);
FLOPPY_DPRINTF("dma_mode=%d direction=%d (%d - %d)\n",
dma_mode, direction,
(128 << fdctrl->fifo[5]) *
(cur_drv->last_sect - ks + 1), fdctrl->data_len);
- if (((direction == FD_DIR_SCANE || direction == FD_DIR_SCANL ||
- direction == FD_DIR_SCANH) && dma_mode == 0) ||
- (direction == FD_DIR_WRITE && dma_mode == 2) ||
- (direction == FD_DIR_READ && dma_mode == 1) ||
- (direction == FD_DIR_VERIFY)) {
+ switch (direction) {
+ case FD_DIR_SCANE:
+ case FD_DIR_SCANL:
+ case FD_DIR_SCANH:
+ dma_mode_ok = (dma_mode == ISADMA_TRANSFER_VERIFY);
+ break;
+ case FD_DIR_WRITE:
+ dma_mode_ok = (dma_mode == ISADMA_TRANSFER_WRITE);
+ break;
+ case FD_DIR_READ:
+ dma_mode_ok = (dma_mode == ISADMA_TRANSFER_READ);
+ break;
+ case FD_DIR_VERIFY:
+ dma_mode_ok = true;
+ break;
+ default:
+ dma_mode_ok = false;
+ break;
+ }
+ if (dma_mode_ok) {
/* No access is allowed until DMA transfer has completed */
fdctrl->msr &= ~FD_MSR_RQM;
if (direction != FD_DIR_VERIFY) {
/* Now, we just have to wait for the DMA controller to
* recall us...
*/
- DMA_hold_DREQ(fdctrl->dma_chann);
- DMA_schedule(fdctrl->dma_chann);
+ k->hold_DREQ(fdctrl->dma, fdctrl->dma_chann);
+ k->schedule(fdctrl->dma);
} else {
/* Start transfer */
fdctrl_transfer_handler(fdctrl, fdctrl->dma_chann, 0,
FDrive *cur_drv;
int len, start_pos, rel_pos;
uint8_t status0 = 0x00, status1 = 0x00, status2 = 0x00;
+ IsaDmaClass *k;
fdctrl = opaque;
if (fdctrl->msr & FD_MSR_RQM) {
FLOPPY_DPRINTF("Not in DMA transfer mode !\n");
return 0;
}
+ k = ISADMA_GET_CLASS(fdctrl->dma);
cur_drv = get_cur_drv(fdctrl);
if (fdctrl->data_dir == FD_DIR_SCANE || fdctrl->data_dir == FD_DIR_SCANL ||
fdctrl->data_dir == FD_DIR_SCANH)
if (fdctrl->data_dir != FD_DIR_WRITE ||
len < FD_SECTOR_LEN || rel_pos != 0) {
/* READ & SCAN commands and realign to a sector for WRITE */
- if (blk_read(cur_drv->blk, fd_sector(cur_drv),
- fdctrl->fifo, 1) < 0) {
+ if (blk_pread(cur_drv->blk, fd_offset(cur_drv),
+ fdctrl->fifo, BDRV_SECTOR_SIZE) < 0) {
FLOPPY_DPRINTF("Floppy: error getting sector %d\n",
fd_sector(cur_drv));
/* Sure, image size is too small... */
switch (fdctrl->data_dir) {
case FD_DIR_READ:
/* READ commands */
- DMA_write_memory (nchan, fdctrl->fifo + rel_pos,
- fdctrl->data_pos, len);
+ k->write_memory(fdctrl->dma, nchan, fdctrl->fifo + rel_pos,
+ fdctrl->data_pos, len);
break;
case FD_DIR_WRITE:
/* WRITE commands */
goto transfer_error;
}
- DMA_read_memory (nchan, fdctrl->fifo + rel_pos,
- fdctrl->data_pos, len);
- if (blk_write(cur_drv->blk, fd_sector(cur_drv),
- fdctrl->fifo, 1) < 0) {
+ k->read_memory(fdctrl->dma, nchan, fdctrl->fifo + rel_pos,
+ fdctrl->data_pos, len);
+ if (blk_pwrite(cur_drv->blk, fd_offset(cur_drv),
+ fdctrl->fifo, BDRV_SECTOR_SIZE, 0) < 0) {
FLOPPY_DPRINTF("error writing sector %d\n",
fd_sector(cur_drv));
fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
{
uint8_t tmpbuf[FD_SECTOR_LEN];
int ret;
- DMA_read_memory (nchan, tmpbuf, fdctrl->data_pos, len);
+ k->read_memory(fdctrl->dma, nchan, tmpbuf, fdctrl->data_pos,
+ len);
ret = memcmp(tmpbuf, fdctrl->fifo + rel_pos, len);
if (ret == 0) {
status2 = FD_SR2_SEH;
fd_sector(cur_drv));
return 0;
}
- if (blk_read(cur_drv->blk, fd_sector(cur_drv), fdctrl->fifo, 1)
+ if (blk_pread(cur_drv->blk, fd_offset(cur_drv), fdctrl->fifo,
+ BDRV_SECTOR_SIZE)
< 0) {
FLOPPY_DPRINTF("error getting sector %d\n",
fd_sector(cur_drv));
}
memset(fdctrl->fifo, 0, FD_SECTOR_LEN);
if (cur_drv->blk == NULL ||
- blk_write(cur_drv->blk, fd_sector(cur_drv), fdctrl->fifo, 1) < 0) {
+ blk_pwrite(cur_drv->blk, fd_offset(cur_drv), fdctrl->fifo,
+ BDRV_SECTOR_SIZE, 0) < 0) {
FLOPPY_DPRINTF("error formatting sector %d\n", fd_sector(cur_drv));
fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
} else {
FDrive *cur_drv = get_cur_drv(fdctrl);
cur_drv->head = (fdctrl->fifo[1] >> 2) & 1;
- timer_mod(fdctrl->result_timer,
- qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (get_ticks_per_sec() / 50));
+ timer_mod(fdctrl->result_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ (NANOSECONDS_PER_SECOND / 50));
}
static void fdctrl_handle_format_track(FDCtrl *fdctrl, int direction)
if (pos == FD_SECTOR_LEN - 1 ||
fdctrl->data_pos == fdctrl->data_len) {
cur_drv = get_cur_drv(fdctrl);
- if (blk_write(cur_drv->blk, fd_sector(cur_drv), fdctrl->fifo, 1)
- < 0) {
+ if (blk_pwrite(cur_drv->blk, fd_offset(cur_drv), fdctrl->fifo,
+ BDRV_SECTOR_SIZE, 0) < 0) {
FLOPPY_DPRINTF("error writing sector %d\n",
fd_sector(cur_drv));
break;
}
}
-static void fdctrl_change_cb(void *opaque, bool load)
-{
- FDrive *drive = opaque;
-
- drive->media_changed = 1;
- fd_revalidate(drive);
-}
-
-static const BlockDevOps fdctrl_block_ops = {
- .change_media_cb = fdctrl_change_cb,
-};
-
/* Init functions */
static void fdctrl_connect_drives(FDCtrl *fdctrl, Error **errp)
{
unsigned int i;
FDrive *drive;
+ DeviceState *dev;
+ Error *local_err = NULL;
for (i = 0; i < MAX_FD; i++) {
drive = &fdctrl->drives[i];
drive->fdctrl = fdctrl;
- if (drive->blk) {
- if (blk_get_on_error(drive->blk, 0) != BLOCKDEV_ON_ERROR_ENOSPC) {
- error_setg(errp, "fdc doesn't support drive option werror");
- return;
- }
- if (blk_get_on_error(drive->blk, 1) != BLOCKDEV_ON_ERROR_REPORT) {
- error_setg(errp, "fdc doesn't support drive option rerror");
- return;
- }
+ /* If the drive is not present, we skip creating the qdev device, but
+ * still have to initialise the controller. */
+ if (!fdctrl->drives[i].blk) {
+ fd_init(drive);
+ fd_revalidate(drive);
+ continue;
}
- fd_init(drive);
- fdctrl_change_cb(drive, 0);
- if (drive->blk) {
- blk_set_dev_ops(drive->blk, &fdctrl_block_ops, drive);
+ dev = qdev_create(&fdctrl->bus.bus, "floppy");
+ qdev_prop_set_uint32(dev, "unit", i);
+ object_property_set_bool(OBJECT(dev), true, "realized", &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
}
}
}
dev = DEVICE(isadev);
if (fds[0]) {
- qdev_prop_set_drive_nofail(dev, "driveA", blk_by_legacy_dinfo(fds[0]));
+ qdev_prop_set_drive(dev, "driveA", blk_by_legacy_dinfo(fds[0]),
+ &error_fatal);
}
if (fds[1]) {
- qdev_prop_set_drive_nofail(dev, "driveB", blk_by_legacy_dinfo(fds[1]));
+ qdev_prop_set_drive(dev, "driveB", blk_by_legacy_dinfo(fds[1]),
+ &error_fatal);
}
qdev_init_nofail(dev);
fdctrl = &sys->state;
fdctrl->dma_chann = dma_chann; /* FIXME */
if (fds[0]) {
- qdev_prop_set_drive_nofail(dev, "driveA", blk_by_legacy_dinfo(fds[0]));
+ qdev_prop_set_drive(dev, "driveA", blk_by_legacy_dinfo(fds[0]),
+ &error_fatal);
}
if (fds[1]) {
- qdev_prop_set_drive_nofail(dev, "driveB", blk_by_legacy_dinfo(fds[1]));
+ qdev_prop_set_drive(dev, "driveB", blk_by_legacy_dinfo(fds[1]),
+ &error_fatal);
}
qdev_init_nofail(dev);
sbd = SYS_BUS_DEVICE(dev);
dev = qdev_create(NULL, "SUNW,fdtwo");
if (fds[0]) {
- qdev_prop_set_drive_nofail(dev, "drive", blk_by_legacy_dinfo(fds[0]));
+ qdev_prop_set_drive(dev, "drive", blk_by_legacy_dinfo(fds[0]),
+ &error_fatal);
}
qdev_init_nofail(dev);
sys = SYSBUS_FDC(dev);
*fdc_tc = qdev_get_gpio_in(dev, 0);
}
-static void fdctrl_realize_common(FDCtrl *fdctrl, Error **errp)
+static void fdctrl_realize_common(DeviceState *dev, FDCtrl *fdctrl,
+ Error **errp)
{
int i, j;
static int command_tables_inited = 0;
+ if (fdctrl->fallback == FLOPPY_DRIVE_TYPE_AUTO) {
+ error_setg(errp, "Cannot choose a fallback FDrive type of 'auto'");
+ }
+
/* Fill 'command_to_handler' lookup table */
if (!command_tables_inited) {
command_tables_inited = 1;
fdctrl->num_floppies = MAX_FD;
if (fdctrl->dma_chann != -1) {
- DMA_register_channel(fdctrl->dma_chann, &fdctrl_transfer_handler, fdctrl);
+ IsaDmaClass *k;
+ assert(fdctrl->dma);
+ k = ISADMA_GET_CLASS(fdctrl->dma);
+ k->register_channel(fdctrl->dma, fdctrl->dma_chann,
+ &fdctrl_transfer_handler, fdctrl);
}
+
+ floppy_bus_create(fdctrl, &fdctrl->bus, dev);
fdctrl_connect_drives(fdctrl, errp);
}
FDCtrl *fdctrl = &isa->state;
Error *err = NULL;
- isa_register_portio_list(isadev, isa->iobase, fdc_portio_list, fdctrl,
+ isa_register_portio_list(isadev, &fdctrl->portio_list,
+ isa->iobase, fdc_portio_list, fdctrl,
"fdc");
isa_init_irq(isadev, &fdctrl->irq, isa->irq);
fdctrl->dma_chann = isa->dma;
+ if (fdctrl->dma_chann != -1) {
+ fdctrl->dma = isa_get_dma(isa_bus_from_device(isadev), isa->dma);
+ assert(fdctrl->dma);
+ }
qdev_set_legacy_instance_id(dev, isa->iobase, 2);
- fdctrl_realize_common(fdctrl, &err);
+ fdctrl_realize_common(dev, fdctrl, &err);
if (err != NULL) {
error_propagate(errp, err);
return;
FDCtrlSysBus *sys = SYSBUS_FDC(obj);
FDCtrl *fdctrl = &sys->state;
+ fdctrl->dma_chann = -1;
+
memory_region_init_io(&fdctrl->iomem, obj, &fdctrl_mem_strict_ops,
fdctrl, "fdctrl", 0x08);
sysbus_init_mmio(sbd, &fdctrl->iomem);
FDCtrlSysBus *sys = SYSBUS_FDC(dev);
FDCtrl *fdctrl = &sys->state;
- fdctrl_realize_common(fdctrl, errp);
+ fdctrl_realize_common(dev, fdctrl, errp);
}
-FDriveType isa_fdc_get_drive_type(ISADevice *fdc, int i)
+FloppyDriveType isa_fdc_get_drive_type(ISADevice *fdc, int i)
{
FDCtrlISABus *isa = ISA_FDC(fdc);
return isa->state.drives[i].drive;
}
+void isa_fdc_get_drive_max_chs(FloppyDriveType type,
+ uint8_t *maxc, uint8_t *maxh, uint8_t *maxs)
+{
+ const FDFormat *fdf;
+
+ *maxc = *maxh = *maxs = 0;
+ for (fdf = fd_formats; fdf->drive != FLOPPY_DRIVE_TYPE_NONE; fdf++) {
+ if (fdf->drive != type) {
+ continue;
+ }
+ if (*maxc < fdf->max_track) {
+ *maxc = fdf->max_track;
+ }
+ if (*maxh < fdf->max_head) {
+ *maxh = fdf->max_head;
+ }
+ if (*maxs < fdf->last_sect) {
+ *maxs = fdf->last_sect;
+ }
+ }
+ (*maxc)--;
+}
+
static const VMStateDescription vmstate_isa_fdc ={
.name = "fdc",
.version_id = 2,
DEFINE_PROP_DRIVE("driveB", FDCtrlISABus, state.drives[1].blk),
DEFINE_PROP_BIT("check_media_rate", FDCtrlISABus, state.check_media_rate,
0, true),
+ DEFINE_PROP_DEFAULT("fdtypeA", FDCtrlISABus, state.drives[0].drive,
+ FLOPPY_DRIVE_TYPE_AUTO, qdev_prop_fdc_drive_type,
+ FloppyDriveType),
+ DEFINE_PROP_DEFAULT("fdtypeB", FDCtrlISABus, state.drives[1].drive,
+ FLOPPY_DRIVE_TYPE_AUTO, qdev_prop_fdc_drive_type,
+ FloppyDriveType),
+ DEFINE_PROP_DEFAULT("fallback", FDCtrlISABus, state.fallback,
+ FLOPPY_DRIVE_TYPE_288, qdev_prop_fdc_drive_type,
+ FloppyDriveType),
DEFINE_PROP_END_OF_LIST(),
};
static Property sysbus_fdc_properties[] = {
DEFINE_PROP_DRIVE("driveA", FDCtrlSysBus, state.drives[0].blk),
DEFINE_PROP_DRIVE("driveB", FDCtrlSysBus, state.drives[1].blk),
+ DEFINE_PROP_DEFAULT("fdtypeA", FDCtrlSysBus, state.drives[0].drive,
+ FLOPPY_DRIVE_TYPE_AUTO, qdev_prop_fdc_drive_type,
+ FloppyDriveType),
+ DEFINE_PROP_DEFAULT("fdtypeB", FDCtrlSysBus, state.drives[1].drive,
+ FLOPPY_DRIVE_TYPE_AUTO, qdev_prop_fdc_drive_type,
+ FloppyDriveType),
+ DEFINE_PROP_DEFAULT("fallback", FDCtrlISABus, state.fallback,
+ FLOPPY_DRIVE_TYPE_144, qdev_prop_fdc_drive_type,
+ FloppyDriveType),
DEFINE_PROP_END_OF_LIST(),
};
static Property sun4m_fdc_properties[] = {
DEFINE_PROP_DRIVE("drive", FDCtrlSysBus, state.drives[0].blk),
+ DEFINE_PROP_DEFAULT("fdtype", FDCtrlSysBus, state.drives[0].drive,
+ FLOPPY_DRIVE_TYPE_AUTO, qdev_prop_fdc_drive_type,
+ FloppyDriveType),
+ DEFINE_PROP_DEFAULT("fallback", FDCtrlISABus, state.fallback,
+ FLOPPY_DRIVE_TYPE_144, qdev_prop_fdc_drive_type,
+ FloppyDriveType),
DEFINE_PROP_END_OF_LIST(),
};
type_register_static(&sysbus_fdc_type_info);
type_register_static(&sysbus_fdc_info);
type_register_static(&sun4m_fdc_info);
+ type_register_static(&floppy_bus_info);
+ type_register_static(&floppy_drive_info);
}
type_init(fdc_register_types)