| 1 | /* |
| 2 | * QEMU Floppy disk emulator (Intel 82078) |
| 3 | * |
| 4 | * Copyright (c) 2003, 2007 Jocelyn Mayer |
| 5 | * Copyright (c) 2008 Hervé Poussineau |
| 6 | * |
| 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 8 | * of this software and associated documentation files (the "Software"), to deal |
| 9 | * in the Software without restriction, including without limitation the rights |
| 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 11 | * copies of the Software, and to permit persons to whom the Software is |
| 12 | * furnished to do so, subject to the following conditions: |
| 13 | * |
| 14 | * The above copyright notice and this permission notice shall be included in |
| 15 | * all copies or substantial portions of the Software. |
| 16 | * |
| 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| 20 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 23 | * THE SOFTWARE. |
| 24 | */ |
| 25 | /* |
| 26 | * The controller is used in Sun4m systems in a slightly different |
| 27 | * way. There are changes in DOR register and DMA is not available. |
| 28 | */ |
| 29 | |
| 30 | #include "qemu/osdep.h" |
| 31 | #include "hw/block/fdc.h" |
| 32 | #include "qapi/error.h" |
| 33 | #include "qemu/error-report.h" |
| 34 | #include "qemu/timer.h" |
| 35 | #include "hw/acpi/acpi_aml_interface.h" |
| 36 | #include "hw/core/irq.h" |
| 37 | #include "hw/isa/isa.h" |
| 38 | #include "hw/core/qdev-properties.h" |
| 39 | #include "hw/core/qdev-properties-system.h" |
| 40 | #include "migration/vmstate.h" |
| 41 | #include "hw/block/block.h" |
| 42 | #include "system/block-backend.h" |
| 43 | #include "system/blockdev.h" |
| 44 | #include "system/system.h" |
| 45 | #include "system/ioport.h" |
| 46 | #include "qemu/log.h" |
| 47 | #include "qemu/main-loop.h" |
| 48 | #include "qemu/module.h" |
| 49 | #include "trace.h" |
| 50 | #include "qom/object.h" |
| 51 | #include "fdc-internal.h" |
| 52 | |
| 53 | OBJECT_DECLARE_SIMPLE_TYPE(FDCtrlISABus, ISA_FDC) |
| 54 | |
| 55 | struct FDCtrlISABus { |
| 56 | /*< private >*/ |
| 57 | ISADevice parent_obj; |
| 58 | /*< public >*/ |
| 59 | |
| 60 | uint32_t iobase; |
| 61 | uint32_t irq; |
| 62 | uint32_t dma; |
| 63 | struct FDCtrl state; |
| 64 | PortioList portio_list; |
| 65 | int32_t bootindexA; |
| 66 | int32_t bootindexB; |
| 67 | }; |
| 68 | |
| 69 | static void fdctrl_external_reset_isa(DeviceState *d) |
| 70 | { |
| 71 | FDCtrlISABus *isa = ISA_FDC(d); |
| 72 | FDCtrl *s = &isa->state; |
| 73 | |
| 74 | fdctrl_reset(s, 0); |
| 75 | } |
| 76 | |
| 77 | void isa_fdc_init_drives(ISADevice *fdc, DriveInfo **fds) |
| 78 | { |
| 79 | fdctrl_init_drives(&ISA_FDC(fdc)->state.bus, fds); |
| 80 | } |
| 81 | |
| 82 | static const MemoryRegionPortio fdc_portio_list[] = { |
| 83 | { 1, 5, 1, .read = fdctrl_read, .write = fdctrl_write }, |
| 84 | { 7, 1, 1, .read = fdctrl_read, .write = fdctrl_write }, |
| 85 | PORTIO_END_OF_LIST(), |
| 86 | }; |
| 87 | |
| 88 | static void isabus_fdc_realize(DeviceState *dev, Error **errp) |
| 89 | { |
| 90 | ISADevice *isadev = ISA_DEVICE(dev); |
| 91 | ISABus *bus = isa_bus_from_device(isadev); |
| 92 | FDCtrlISABus *isa = ISA_FDC(dev); |
| 93 | FDCtrl *fdctrl = &isa->state; |
| 94 | Error *err = NULL; |
| 95 | |
| 96 | isa_register_portio_list(isadev, &isa->portio_list, |
| 97 | isa->iobase, fdc_portio_list, fdctrl, |
| 98 | "fdc"); |
| 99 | |
| 100 | fdctrl->irq = isa_bus_get_irq(bus, isa->irq); |
| 101 | fdctrl->dma_chann = isa->dma; |
| 102 | if (fdctrl->dma_chann != -1) { |
| 103 | IsaDmaClass *k; |
| 104 | fdctrl->dma = isa_bus_get_dma(bus, isa->dma); |
| 105 | if (!fdctrl->dma) { |
| 106 | error_setg(errp, "ISA controller does not support DMA"); |
| 107 | return; |
| 108 | } |
| 109 | k = ISADMA_GET_CLASS(fdctrl->dma); |
| 110 | k->register_channel(fdctrl->dma, fdctrl->dma_chann, |
| 111 | &fdctrl_transfer_handler, fdctrl); |
| 112 | } |
| 113 | |
| 114 | qdev_set_legacy_instance_id(dev, isa->iobase, 2); |
| 115 | |
| 116 | fdctrl_realize_common(dev, fdctrl, &err); |
| 117 | if (err != NULL) { |
| 118 | error_propagate(errp, err); |
| 119 | return; |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | FloppyDriveType isa_fdc_get_drive_type(ISADevice *fdc, int i) |
| 124 | { |
| 125 | FDCtrlISABus *isa = ISA_FDC(fdc); |
| 126 | |
| 127 | return isa->state.drives[i].drive; |
| 128 | } |
| 129 | |
| 130 | static void isa_fdc_get_drive_max_chs(FloppyDriveType type, uint8_t *maxc, |
| 131 | uint8_t *maxh, uint8_t *maxs) |
| 132 | { |
| 133 | const FDFormat *fdf; |
| 134 | |
| 135 | *maxc = *maxh = *maxs = 0; |
| 136 | for (fdf = fd_formats; fdf->drive != FLOPPY_DRIVE_TYPE_NONE; fdf++) { |
| 137 | if (fdf->drive != type) { |
| 138 | continue; |
| 139 | } |
| 140 | if (*maxc < fdf->max_track) { |
| 141 | *maxc = fdf->max_track; |
| 142 | } |
| 143 | if (*maxh < fdf->max_head) { |
| 144 | *maxh = fdf->max_head; |
| 145 | } |
| 146 | if (*maxs < fdf->last_sect) { |
| 147 | *maxs = fdf->last_sect; |
| 148 | } |
| 149 | } |
| 150 | /* fd_formats must contain at least one entry per FloppyDriveType */ |
| 151 | assert(*maxc); |
| 152 | (*maxc)--; |
| 153 | } |
| 154 | |
| 155 | static Aml *build_fdinfo_aml(int idx, FloppyDriveType type) |
| 156 | { |
| 157 | Aml *dev, *fdi; |
| 158 | uint8_t maxc, maxh, maxs; |
| 159 | |
| 160 | isa_fdc_get_drive_max_chs(type, &maxc, &maxh, &maxs); |
| 161 | |
| 162 | dev = aml_device("FLP%c", 'A' + idx); |
| 163 | |
| 164 | aml_append(dev, aml_name_decl("_ADR", aml_int(idx))); |
| 165 | |
| 166 | fdi = aml_package(16); |
| 167 | aml_append(fdi, aml_int(idx)); /* Drive Number */ |
| 168 | aml_append(fdi, |
| 169 | aml_int(cmos_get_fd_drive_type(type))); /* Device Type */ |
| 170 | /* |
| 171 | * the values below are the limits of the drive, and are thus independent |
| 172 | * of the inserted media |
| 173 | */ |
| 174 | aml_append(fdi, aml_int(maxc)); /* Maximum Cylinder Number */ |
| 175 | aml_append(fdi, aml_int(maxs)); /* Maximum Sector Number */ |
| 176 | aml_append(fdi, aml_int(maxh)); /* Maximum Head Number */ |
| 177 | /* |
| 178 | * SeaBIOS returns the below values for int 0x13 func 0x08 regardless of |
| 179 | * the drive type, so shall we |
| 180 | */ |
| 181 | aml_append(fdi, aml_int(0xAF)); /* disk_specify_1 */ |
| 182 | aml_append(fdi, aml_int(0x02)); /* disk_specify_2 */ |
| 183 | aml_append(fdi, aml_int(0x25)); /* disk_motor_wait */ |
| 184 | aml_append(fdi, aml_int(0x02)); /* disk_sector_siz */ |
| 185 | aml_append(fdi, aml_int(0x12)); /* disk_eot */ |
| 186 | aml_append(fdi, aml_int(0x1B)); /* disk_rw_gap */ |
| 187 | aml_append(fdi, aml_int(0xFF)); /* disk_dtl */ |
| 188 | aml_append(fdi, aml_int(0x6C)); /* disk_formt_gap */ |
| 189 | aml_append(fdi, aml_int(0xF6)); /* disk_fill */ |
| 190 | aml_append(fdi, aml_int(0x0F)); /* disk_head_sttl */ |
| 191 | aml_append(fdi, aml_int(0x08)); /* disk_motor_strt */ |
| 192 | |
| 193 | aml_append(dev, aml_name_decl("_FDI", fdi)); |
| 194 | return dev; |
| 195 | } |
| 196 | |
| 197 | void isa_fdc_set_iobase(ISADevice *fdc, hwaddr iobase) |
| 198 | { |
| 199 | FDCtrlISABus *isa = ISA_FDC(fdc); |
| 200 | |
| 201 | fdc->ioport_id = iobase; |
| 202 | isa->iobase = iobase; |
| 203 | portio_list_set_address(&isa->portio_list, isa->iobase); |
| 204 | } |
| 205 | |
| 206 | void isa_fdc_set_enabled(ISADevice *fdc, bool enabled) |
| 207 | { |
| 208 | portio_list_set_enabled(&ISA_FDC(fdc)->portio_list, enabled); |
| 209 | } |
| 210 | |
| 211 | int cmos_get_fd_drive_type(FloppyDriveType fd0) |
| 212 | { |
| 213 | int val; |
| 214 | |
| 215 | switch (fd0) { |
| 216 | case FLOPPY_DRIVE_TYPE_144: |
| 217 | /* 1.44 Mb 3"5 drive */ |
| 218 | val = 4; |
| 219 | break; |
| 220 | case FLOPPY_DRIVE_TYPE_288: |
| 221 | /* 2.88 Mb 3"5 drive */ |
| 222 | val = 5; |
| 223 | break; |
| 224 | case FLOPPY_DRIVE_TYPE_120: |
| 225 | /* 1.2 Mb 5"5 drive */ |
| 226 | val = 2; |
| 227 | break; |
| 228 | case FLOPPY_DRIVE_TYPE_NONE: |
| 229 | default: |
| 230 | val = 0; |
| 231 | break; |
| 232 | } |
| 233 | return val; |
| 234 | } |
| 235 | |
| 236 | static void build_fdc_aml(AcpiDevAmlIf *adev, Aml *scope) |
| 237 | { |
| 238 | FDCtrlISABus *isa = ISA_FDC(adev); |
| 239 | Aml *dev; |
| 240 | Aml *crs; |
| 241 | int i; |
| 242 | |
| 243 | #define ACPI_FDE_MAX_FD 4 |
| 244 | uint32_t fde_buf[5] = { |
| 245 | 0, 0, 0, 0, /* presence of floppy drives #0 - #3 */ |
| 246 | cpu_to_le32(2) /* tape presence (2 == never present) */ |
| 247 | }; |
| 248 | |
| 249 | crs = aml_resource_template(); |
| 250 | aml_append(crs, |
| 251 | aml_io(AML_DECODE16, isa->iobase + 2, isa->iobase + 2, 0x00, 0x04)); |
| 252 | aml_append(crs, |
| 253 | aml_io(AML_DECODE16, isa->iobase + 7, isa->iobase + 7, 0x00, 0x01)); |
| 254 | aml_append(crs, aml_irq_no_flags(isa->irq)); |
| 255 | aml_append(crs, |
| 256 | aml_dma(AML_COMPATIBILITY, AML_NOTBUSMASTER, AML_TRANSFER8, isa->dma)); |
| 257 | |
| 258 | dev = aml_device("FDC0"); |
| 259 | aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0700"))); |
| 260 | aml_append(dev, aml_name_decl("_CRS", crs)); |
| 261 | |
| 262 | for (i = 0; i < MIN(MAX_FD, ACPI_FDE_MAX_FD); i++) { |
| 263 | FloppyDriveType type = isa_fdc_get_drive_type(ISA_DEVICE(adev), i); |
| 264 | |
| 265 | if (type < FLOPPY_DRIVE_TYPE_NONE) { |
| 266 | fde_buf[i] = cpu_to_le32(1); /* drive present */ |
| 267 | aml_append(dev, build_fdinfo_aml(i, type)); |
| 268 | } |
| 269 | } |
| 270 | aml_append(dev, aml_name_decl("_FDE", |
| 271 | aml_buffer(sizeof(fde_buf), (uint8_t *)fde_buf))); |
| 272 | |
| 273 | aml_append(scope, dev); |
| 274 | } |
| 275 | |
| 276 | static const VMStateDescription vmstate_isa_fdc = { |
| 277 | .name = "fdc", |
| 278 | .version_id = 2, |
| 279 | .minimum_version_id = 2, |
| 280 | .fields = (const VMStateField[]) { |
| 281 | VMSTATE_STRUCT(state, FDCtrlISABus, 0, vmstate_fdc, FDCtrl), |
| 282 | VMSTATE_END_OF_LIST() |
| 283 | } |
| 284 | }; |
| 285 | |
| 286 | static const Property isa_fdc_properties[] = { |
| 287 | DEFINE_PROP_UINT32("iobase", FDCtrlISABus, iobase, 0x3f0), |
| 288 | DEFINE_PROP_UINT32("irq", FDCtrlISABus, irq, 6), |
| 289 | DEFINE_PROP_UINT32("dma", FDCtrlISABus, dma, 2), |
| 290 | DEFINE_PROP_SIGNED("fdtypeA", FDCtrlISABus, state.qdev_for_drives[0].type, |
| 291 | FLOPPY_DRIVE_TYPE_AUTO, qdev_prop_fdc_drive_type, |
| 292 | FloppyDriveType), |
| 293 | DEFINE_PROP_SIGNED("fdtypeB", FDCtrlISABus, state.qdev_for_drives[1].type, |
| 294 | FLOPPY_DRIVE_TYPE_AUTO, qdev_prop_fdc_drive_type, |
| 295 | FloppyDriveType), |
| 296 | DEFINE_PROP_SIGNED("fallback", FDCtrlISABus, state.fallback, |
| 297 | FLOPPY_DRIVE_TYPE_288, qdev_prop_fdc_drive_type, |
| 298 | FloppyDriveType), |
| 299 | }; |
| 300 | |
| 301 | static void isabus_fdc_class_init(ObjectClass *klass, const void *data) |
| 302 | { |
| 303 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 304 | AcpiDevAmlIfClass *adevc = ACPI_DEV_AML_IF_CLASS(klass); |
| 305 | |
| 306 | dc->desc = "virtual floppy controller"; |
| 307 | dc->realize = isabus_fdc_realize; |
| 308 | dc->fw_name = "fdc"; |
| 309 | device_class_set_legacy_reset(dc, fdctrl_external_reset_isa); |
| 310 | dc->vmsd = &vmstate_isa_fdc; |
| 311 | adevc->build_dev_aml = build_fdc_aml; |
| 312 | device_class_set_props(dc, isa_fdc_properties); |
| 313 | set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); |
| 314 | } |
| 315 | |
| 316 | static void isabus_fdc_instance_init(Object *obj) |
| 317 | { |
| 318 | FDCtrlISABus *isa = ISA_FDC(obj); |
| 319 | |
| 320 | device_add_bootindex_property(obj, &isa->bootindexA, |
| 321 | "bootindexA", "/floppy@0", |
| 322 | DEVICE(obj)); |
| 323 | device_add_bootindex_property(obj, &isa->bootindexB, |
| 324 | "bootindexB", "/floppy@1", |
| 325 | DEVICE(obj)); |
| 326 | } |
| 327 | |
| 328 | static const TypeInfo isa_fdc_info = { |
| 329 | .name = TYPE_ISA_FDC, |
| 330 | .parent = TYPE_ISA_DEVICE, |
| 331 | .instance_size = sizeof(FDCtrlISABus), |
| 332 | .class_init = isabus_fdc_class_init, |
| 333 | .instance_init = isabus_fdc_instance_init, |
| 334 | .interfaces = (const InterfaceInfo[]) { |
| 335 | { TYPE_ACPI_DEV_AML_IF }, |
| 336 | { }, |
| 337 | }, |
| 338 | }; |
| 339 | |
| 340 | static void isa_fdc_register_types(void) |
| 341 | { |
| 342 | type_register_static(&isa_fdc_info); |
| 343 | } |
| 344 | |
| 345 | type_init(isa_fdc_register_types) |