| /* |
| * QEMU Floppy disk emulator (Intel 82078) |
| * |
| * Copyright (c) 2003, 2007 Jocelyn Mayer |
| * Copyright (c) 2008 Hervé Poussineau |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| */ |
| /* |
| * The controller is used in Sun4m systems in a slightly different |
| * way. There are changes in DOR register and DMA is not available. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "hw/block/fdc.h" |
| #include "qapi/error.h" |
| #include "qemu/error-report.h" |
| #include "qemu/timer.h" |
| #include "hw/acpi/acpi_aml_interface.h" |
| #include "hw/irq.h" |
| #include "hw/isa/isa.h" |
| #include "hw/qdev-properties.h" |
| #include "hw/qdev-properties-system.h" |
| #include "migration/vmstate.h" |
| #include "hw/block/block.h" |
| #include "sysemu/block-backend.h" |
| #include "sysemu/blockdev.h" |
| #include "sysemu/sysemu.h" |
| #include "exec/ioport.h" |
| #include "qemu/log.h" |
| #include "qemu/main-loop.h" |
| #include "qemu/module.h" |
| #include "trace.h" |
| #include "qom/object.h" |
| #include "fdc-internal.h" |
| |
| OBJECT_DECLARE_SIMPLE_TYPE(FDCtrlISABus, ISA_FDC) |
| |
| struct FDCtrlISABus { |
| /*< private >*/ |
| ISADevice parent_obj; |
| /*< public >*/ |
| |
| uint32_t iobase; |
| uint32_t irq; |
| uint32_t dma; |
| struct FDCtrl state; |
| PortioList portio_list; |
| int32_t bootindexA; |
| int32_t bootindexB; |
| }; |
| |
| static void fdctrl_external_reset_isa(DeviceState *d) |
| { |
| FDCtrlISABus *isa = ISA_FDC(d); |
| FDCtrl *s = &isa->state; |
| |
| fdctrl_reset(s, 0); |
| } |
| |
| void isa_fdc_init_drives(ISADevice *fdc, DriveInfo **fds) |
| { |
| fdctrl_init_drives(&ISA_FDC(fdc)->state.bus, fds); |
| } |
| |
| static const MemoryRegionPortio fdc_portio_list[] = { |
| { 1, 5, 1, .read = fdctrl_read, .write = fdctrl_write }, |
| { 7, 1, 1, .read = fdctrl_read, .write = fdctrl_write }, |
| PORTIO_END_OF_LIST(), |
| }; |
| |
| static void isabus_fdc_realize(DeviceState *dev, Error **errp) |
| { |
| ISADevice *isadev = ISA_DEVICE(dev); |
| ISABus *bus = isa_bus_from_device(isadev); |
| FDCtrlISABus *isa = ISA_FDC(dev); |
| FDCtrl *fdctrl = &isa->state; |
| Error *err = NULL; |
| |
| isa_register_portio_list(isadev, &isa->portio_list, |
| isa->iobase, fdc_portio_list, fdctrl, |
| "fdc"); |
| |
| fdctrl->irq = isa_bus_get_irq(bus, isa->irq); |
| fdctrl->dma_chann = isa->dma; |
| if (fdctrl->dma_chann != -1) { |
| IsaDmaClass *k; |
| fdctrl->dma = isa_bus_get_dma(bus, isa->dma); |
| if (!fdctrl->dma) { |
| error_setg(errp, "ISA controller does not support DMA"); |
| return; |
| } |
| k = ISADMA_GET_CLASS(fdctrl->dma); |
| k->register_channel(fdctrl->dma, fdctrl->dma_chann, |
| &fdctrl_transfer_handler, fdctrl); |
| } |
| |
| qdev_set_legacy_instance_id(dev, isa->iobase, 2); |
| |
| fdctrl_realize_common(dev, fdctrl, &err); |
| if (err != NULL) { |
| error_propagate(errp, err); |
| return; |
| } |
| } |
| |
| FloppyDriveType isa_fdc_get_drive_type(ISADevice *fdc, int i) |
| { |
| FDCtrlISABus *isa = ISA_FDC(fdc); |
| |
| return isa->state.drives[i].drive; |
| } |
| |
| static 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 Aml *build_fdinfo_aml(int idx, FloppyDriveType type) |
| { |
| Aml *dev, *fdi; |
| uint8_t maxc, maxh, maxs; |
| |
| isa_fdc_get_drive_max_chs(type, &maxc, &maxh, &maxs); |
| |
| dev = aml_device("FLP%c", 'A' + idx); |
| |
| aml_append(dev, aml_name_decl("_ADR", aml_int(idx))); |
| |
| fdi = aml_package(16); |
| aml_append(fdi, aml_int(idx)); /* Drive Number */ |
| aml_append(fdi, |
| aml_int(cmos_get_fd_drive_type(type))); /* Device Type */ |
| /* |
| * the values below are the limits of the drive, and are thus independent |
| * of the inserted media |
| */ |
| aml_append(fdi, aml_int(maxc)); /* Maximum Cylinder Number */ |
| aml_append(fdi, aml_int(maxs)); /* Maximum Sector Number */ |
| aml_append(fdi, aml_int(maxh)); /* Maximum Head Number */ |
| /* |
| * SeaBIOS returns the below values for int 0x13 func 0x08 regardless of |
| * the drive type, so shall we |
| */ |
| aml_append(fdi, aml_int(0xAF)); /* disk_specify_1 */ |
| aml_append(fdi, aml_int(0x02)); /* disk_specify_2 */ |
| aml_append(fdi, aml_int(0x25)); /* disk_motor_wait */ |
| aml_append(fdi, aml_int(0x02)); /* disk_sector_siz */ |
| aml_append(fdi, aml_int(0x12)); /* disk_eot */ |
| aml_append(fdi, aml_int(0x1B)); /* disk_rw_gap */ |
| aml_append(fdi, aml_int(0xFF)); /* disk_dtl */ |
| aml_append(fdi, aml_int(0x6C)); /* disk_formt_gap */ |
| aml_append(fdi, aml_int(0xF6)); /* disk_fill */ |
| aml_append(fdi, aml_int(0x0F)); /* disk_head_sttl */ |
| aml_append(fdi, aml_int(0x08)); /* disk_motor_strt */ |
| |
| aml_append(dev, aml_name_decl("_FDI", fdi)); |
| return dev; |
| } |
| |
| void isa_fdc_set_iobase(ISADevice *fdc, hwaddr iobase) |
| { |
| FDCtrlISABus *isa = ISA_FDC(fdc); |
| |
| fdc->ioport_id = iobase; |
| isa->iobase = iobase; |
| portio_list_set_address(&isa->portio_list, isa->iobase); |
| } |
| |
| void isa_fdc_set_enabled(ISADevice *fdc, bool enabled) |
| { |
| portio_list_set_enabled(&ISA_FDC(fdc)->portio_list, enabled); |
| } |
| |
| int cmos_get_fd_drive_type(FloppyDriveType fd0) |
| { |
| int val; |
| |
| switch (fd0) { |
| case FLOPPY_DRIVE_TYPE_144: |
| /* 1.44 Mb 3"5 drive */ |
| val = 4; |
| break; |
| case FLOPPY_DRIVE_TYPE_288: |
| /* 2.88 Mb 3"5 drive */ |
| val = 5; |
| break; |
| case FLOPPY_DRIVE_TYPE_120: |
| /* 1.2 Mb 5"5 drive */ |
| val = 2; |
| break; |
| case FLOPPY_DRIVE_TYPE_NONE: |
| default: |
| val = 0; |
| break; |
| } |
| return val; |
| } |
| |
| static void build_fdc_aml(AcpiDevAmlIf *adev, Aml *scope) |
| { |
| FDCtrlISABus *isa = ISA_FDC(adev); |
| Aml *dev; |
| Aml *crs; |
| int i; |
| |
| #define ACPI_FDE_MAX_FD 4 |
| uint32_t fde_buf[5] = { |
| 0, 0, 0, 0, /* presence of floppy drives #0 - #3 */ |
| cpu_to_le32(2) /* tape presence (2 == never present) */ |
| }; |
| |
| crs = aml_resource_template(); |
| aml_append(crs, |
| aml_io(AML_DECODE16, isa->iobase + 2, isa->iobase + 2, 0x00, 0x04)); |
| aml_append(crs, |
| aml_io(AML_DECODE16, isa->iobase + 7, isa->iobase + 7, 0x00, 0x01)); |
| aml_append(crs, aml_irq_no_flags(isa->irq)); |
| aml_append(crs, |
| aml_dma(AML_COMPATIBILITY, AML_NOTBUSMASTER, AML_TRANSFER8, isa->dma)); |
| |
| dev = aml_device("FDC0"); |
| aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0700"))); |
| aml_append(dev, aml_name_decl("_CRS", crs)); |
| |
| for (i = 0; i < MIN(MAX_FD, ACPI_FDE_MAX_FD); i++) { |
| FloppyDriveType type = isa_fdc_get_drive_type(ISA_DEVICE(adev), i); |
| |
| if (type < FLOPPY_DRIVE_TYPE_NONE) { |
| fde_buf[i] = cpu_to_le32(1); /* drive present */ |
| aml_append(dev, build_fdinfo_aml(i, type)); |
| } |
| } |
| aml_append(dev, aml_name_decl("_FDE", |
| aml_buffer(sizeof(fde_buf), (uint8_t *)fde_buf))); |
| |
| aml_append(scope, dev); |
| } |
| |
| static const VMStateDescription vmstate_isa_fdc = { |
| .name = "fdc", |
| .version_id = 2, |
| .minimum_version_id = 2, |
| .fields = (const VMStateField[]) { |
| VMSTATE_STRUCT(state, FDCtrlISABus, 0, vmstate_fdc, FDCtrl), |
| VMSTATE_END_OF_LIST() |
| } |
| }; |
| |
| static Property isa_fdc_properties[] = { |
| DEFINE_PROP_UINT32("iobase", FDCtrlISABus, iobase, 0x3f0), |
| DEFINE_PROP_UINT32("irq", FDCtrlISABus, irq, 6), |
| DEFINE_PROP_UINT32("dma", FDCtrlISABus, dma, 2), |
| DEFINE_PROP_SIGNED("fdtypeA", FDCtrlISABus, state.qdev_for_drives[0].type, |
| FLOPPY_DRIVE_TYPE_AUTO, qdev_prop_fdc_drive_type, |
| FloppyDriveType), |
| DEFINE_PROP_SIGNED("fdtypeB", FDCtrlISABus, state.qdev_for_drives[1].type, |
| FLOPPY_DRIVE_TYPE_AUTO, qdev_prop_fdc_drive_type, |
| FloppyDriveType), |
| DEFINE_PROP_SIGNED("fallback", FDCtrlISABus, state.fallback, |
| FLOPPY_DRIVE_TYPE_288, qdev_prop_fdc_drive_type, |
| FloppyDriveType), |
| DEFINE_PROP_END_OF_LIST(), |
| }; |
| |
| static void isabus_fdc_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| AcpiDevAmlIfClass *adevc = ACPI_DEV_AML_IF_CLASS(klass); |
| |
| dc->desc = "virtual floppy controller"; |
| dc->realize = isabus_fdc_realize; |
| dc->fw_name = "fdc"; |
| dc->reset = fdctrl_external_reset_isa; |
| dc->vmsd = &vmstate_isa_fdc; |
| adevc->build_dev_aml = build_fdc_aml; |
| device_class_set_props(dc, isa_fdc_properties); |
| set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); |
| } |
| |
| static void isabus_fdc_instance_init(Object *obj) |
| { |
| FDCtrlISABus *isa = ISA_FDC(obj); |
| |
| device_add_bootindex_property(obj, &isa->bootindexA, |
| "bootindexA", "/floppy@0", |
| DEVICE(obj)); |
| device_add_bootindex_property(obj, &isa->bootindexB, |
| "bootindexB", "/floppy@1", |
| DEVICE(obj)); |
| } |
| |
| static const TypeInfo isa_fdc_info = { |
| .name = TYPE_ISA_FDC, |
| .parent = TYPE_ISA_DEVICE, |
| .instance_size = sizeof(FDCtrlISABus), |
| .class_init = isabus_fdc_class_init, |
| .instance_init = isabus_fdc_instance_init, |
| .interfaces = (InterfaceInfo[]) { |
| { TYPE_ACPI_DEV_AML_IF }, |
| { }, |
| }, |
| }; |
| |
| static void isa_fdc_register_types(void) |
| { |
| type_register_static(&isa_fdc_info); |
| } |
| |
| type_init(isa_fdc_register_types) |